@kaddo/cli 3.22.0 → 3.23.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 +2 -0
- package/dist/index.js +393 -39
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -509,6 +509,8 @@ create --from roadmap → owners → guard → explain`.
|
|
|
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
511
|
| v3.22 | Guard & graph scope semantics: graph scope metadata, contextual-empty messaging, Guard ownership scope (active + completed; `--include-archived`) |
|
|
512
|
+
| v3.22.1 | Guard project-root path normalization: matches `code:` globs when the project is a subfolder of the Git root |
|
|
513
|
+
| v3.23 | Knowledge Impact Report: `kaddo report impact` (Markdown/JSON, `--output`); evidence-first health/coverage/traceability/readiness/signals; MCP resource + tool |
|
|
512
514
|
|
|
513
515
|
**Optional modules (installed with `kaddo add`):**
|
|
514
516
|
|
package/dist/index.js
CHANGED
|
@@ -592,8 +592,8 @@ function loadConfig(dir) {
|
|
|
592
592
|
const parsed = configSchema.safeParse(raw ?? {});
|
|
593
593
|
if (!parsed.success) {
|
|
594
594
|
const issues = parsed.error.issues.map((i) => {
|
|
595
|
-
const
|
|
596
|
-
return
|
|
595
|
+
const path6 = i.path.join(".");
|
|
596
|
+
return path6 ? ` - ${path6}: ${i.message}` : ` - ${i.message}`;
|
|
597
597
|
}).join("\n");
|
|
598
598
|
throw new ConfigError(`Invalid .kaddo/config.yml:
|
|
599
599
|
${issues}`);
|
|
@@ -684,6 +684,10 @@ var COMMAND_HELP = {
|
|
|
684
684
|
"graph export": {
|
|
685
685
|
question: "How is my project knowledge connected?",
|
|
686
686
|
next: "Open .kaddo/graph.mmd, or run kaddo explain for a summary"
|
|
687
|
+
},
|
|
688
|
+
"report impact": {
|
|
689
|
+
question: "What impact is Kaddo having on this project?",
|
|
690
|
+
next: "Act on the Suggested Actions section"
|
|
687
691
|
}
|
|
688
692
|
};
|
|
689
693
|
function commandFooterLines(name) {
|
|
@@ -4902,6 +4906,14 @@ async function isGitRepo(dir) {
|
|
|
4902
4906
|
return false;
|
|
4903
4907
|
}
|
|
4904
4908
|
}
|
|
4909
|
+
async function getGitRoot(dir) {
|
|
4910
|
+
try {
|
|
4911
|
+
const result = await execa("git", ["rev-parse", "--show-toplevel"], { cwd: dir });
|
|
4912
|
+
return result.stdout.trim() || null;
|
|
4913
|
+
} catch {
|
|
4914
|
+
return null;
|
|
4915
|
+
}
|
|
4916
|
+
}
|
|
4905
4917
|
async function getUntrackedFiles() {
|
|
4906
4918
|
try {
|
|
4907
4919
|
const result = await execa("git", ["ls-files", "--others", "--exclude-standard"]);
|
|
@@ -5048,6 +5060,20 @@ function discoverWorkItems(dir) {
|
|
|
5048
5060
|
|
|
5049
5061
|
// src/core/diff-analysis.ts
|
|
5050
5062
|
import { minimatch } from "minimatch";
|
|
5063
|
+
import path3 from "path";
|
|
5064
|
+
function toPosix2(p2) {
|
|
5065
|
+
return p2.replace(/\\/g, "/");
|
|
5066
|
+
}
|
|
5067
|
+
function normalizeTouchedPathForProject(touchedPath, gitRoot, projectRoot) {
|
|
5068
|
+
const t = toPosix2(touchedPath).replace(/^\.\//, "").replace(/^\/+/, "");
|
|
5069
|
+
if (!gitRoot) return t;
|
|
5070
|
+
const prefix = toPosix2(path3.relative(gitRoot, projectRoot)).replace(/^\.\//, "").replace(/\/+$/, "");
|
|
5071
|
+
if (prefix === "" || prefix === ".") return t;
|
|
5072
|
+
if (prefix.startsWith("..")) return t;
|
|
5073
|
+
if (t === prefix) return null;
|
|
5074
|
+
if (t.startsWith(prefix + "/")) return t.slice(prefix.length + 1);
|
|
5075
|
+
return null;
|
|
5076
|
+
}
|
|
5051
5077
|
function normalizePath(p2) {
|
|
5052
5078
|
return p2.replace(/\\/g, "/");
|
|
5053
5079
|
}
|
|
@@ -5107,10 +5133,10 @@ function analyzeGuard(touchedFiles, artifacts, silentWithoutOwnership) {
|
|
|
5107
5133
|
import { parse as parseYaml4, stringify as stringifyYaml2 } from "yaml";
|
|
5108
5134
|
var IGNORE_FILE = ".kaddo/ignores.yml";
|
|
5109
5135
|
function loadIgnores(dir) {
|
|
5110
|
-
const
|
|
5111
|
-
if (!exists(
|
|
5136
|
+
const path6 = join(dir, IGNORE_FILE);
|
|
5137
|
+
if (!exists(path6)) return [];
|
|
5112
5138
|
try {
|
|
5113
|
-
const raw = readFile(
|
|
5139
|
+
const raw = readFile(path6);
|
|
5114
5140
|
const parsed = parseYaml4(raw);
|
|
5115
5141
|
return Array.isArray(parsed) ? parsed : [];
|
|
5116
5142
|
} catch {
|
|
@@ -5118,7 +5144,7 @@ function loadIgnores(dir) {
|
|
|
5118
5144
|
}
|
|
5119
5145
|
}
|
|
5120
5146
|
function saveIgnore(dir, entry) {
|
|
5121
|
-
const
|
|
5147
|
+
const path6 = join(dir, IGNORE_FILE);
|
|
5122
5148
|
const existing = loadIgnores(dir);
|
|
5123
5149
|
const idx = existing.findIndex((e) => e.artifact_id === entry.artifact_id);
|
|
5124
5150
|
if (idx >= 0) {
|
|
@@ -5126,17 +5152,17 @@ function saveIgnore(dir, entry) {
|
|
|
5126
5152
|
} else {
|
|
5127
5153
|
existing.push(entry);
|
|
5128
5154
|
}
|
|
5129
|
-
writeFile(
|
|
5155
|
+
writeFile(path6, stringifyYaml2(existing));
|
|
5130
5156
|
}
|
|
5131
5157
|
function isIgnored(ignores, artifactId) {
|
|
5132
5158
|
return ignores.find((e) => e.artifact_id === artifactId);
|
|
5133
5159
|
}
|
|
5134
5160
|
function removeIgnore(dir, artifactId) {
|
|
5135
|
-
const
|
|
5161
|
+
const path6 = join(dir, IGNORE_FILE);
|
|
5136
5162
|
const existing = loadIgnores(dir);
|
|
5137
5163
|
const filtered = existing.filter((e) => e.artifact_id !== artifactId);
|
|
5138
5164
|
if (filtered.length === existing.length) return false;
|
|
5139
|
-
writeFile(
|
|
5165
|
+
writeFile(path6, stringifyYaml2(filtered));
|
|
5140
5166
|
return true;
|
|
5141
5167
|
}
|
|
5142
5168
|
|
|
@@ -5158,11 +5184,11 @@ function moduleArtifactCoverage(dir, id) {
|
|
|
5158
5184
|
};
|
|
5159
5185
|
}
|
|
5160
5186
|
function loadMappedModules(dir) {
|
|
5161
|
-
const
|
|
5162
|
-
if (!exists(
|
|
5187
|
+
const path6 = join(dir, DESCRIPTOR_PATH);
|
|
5188
|
+
if (!exists(path6)) return [];
|
|
5163
5189
|
let parsed;
|
|
5164
5190
|
try {
|
|
5165
|
-
parsed = parseYaml5(readFile(
|
|
5191
|
+
parsed = parseYaml5(readFile(path6));
|
|
5166
5192
|
} catch {
|
|
5167
5193
|
return [];
|
|
5168
5194
|
}
|
|
@@ -5254,7 +5280,7 @@ async function collectWorkspaceChanges(dir, mode = "head", deps = defaultDeps) {
|
|
|
5254
5280
|
}
|
|
5255
5281
|
|
|
5256
5282
|
// src/commands/guard.ts
|
|
5257
|
-
import
|
|
5283
|
+
import path4 from "path";
|
|
5258
5284
|
import { parse as parseYaml7 } from "yaml";
|
|
5259
5285
|
|
|
5260
5286
|
// src/plugins/prisma.ts
|
|
@@ -5458,7 +5484,7 @@ function printHeader(touchedFiles) {
|
|
|
5458
5484
|
function artifactLabel(artifact, dir) {
|
|
5459
5485
|
if (artifact.id) return artifact.id;
|
|
5460
5486
|
if (artifact.title) return artifact.title;
|
|
5461
|
-
const rel =
|
|
5487
|
+
const rel = path4.relative(dir, artifact.filePath).split(path4.sep).join("/");
|
|
5462
5488
|
return rel || artifact.filePath;
|
|
5463
5489
|
}
|
|
5464
5490
|
function printWorkspaceHeader(scan2) {
|
|
@@ -5524,7 +5550,7 @@ async function offerIgnore(dir, match) {
|
|
|
5524
5550
|
log2.success(`${id} added to .kaddo/ignores.yml`);
|
|
5525
5551
|
return true;
|
|
5526
5552
|
}
|
|
5527
|
-
function printCIJson(dir, touchedFiles, activeMatches, ignoredCount, pluginSignals, affectedOwners, workspace, includeArchived) {
|
|
5553
|
+
function printCIJson(dir, touchedFiles, activeMatches, ignoredCount, pluginSignals, affectedOwners, workspace, includeArchived, normalizedFiles, outsideProjectFiles) {
|
|
5528
5554
|
const output = {
|
|
5529
5555
|
kaddo_guard: true,
|
|
5530
5556
|
ci: true,
|
|
@@ -5532,6 +5558,9 @@ function printCIJson(dir, touchedFiles, activeMatches, ignoredCount, pluginSigna
|
|
|
5532
5558
|
included: includeArchived ? ["draft", "ready", "in-progress", "blocked", "completed", "archived"] : ["draft", "ready", "in-progress", "blocked", "completed"],
|
|
5533
5559
|
excluded: includeArchived ? [] : ["archived"]
|
|
5534
5560
|
},
|
|
5561
|
+
// Git-root-relative → project-root-relative mapping used for matching (VS-060.1).
|
|
5562
|
+
normalized_files: normalizedFiles,
|
|
5563
|
+
files_outside_project: outsideProjectFiles,
|
|
5535
5564
|
touched_files: touchedFiles.length,
|
|
5536
5565
|
fyi_count: activeMatches.length,
|
|
5537
5566
|
ignored_count: ignoredCount,
|
|
@@ -5580,8 +5609,16 @@ async function runGuard(opts = {}) {
|
|
|
5580
5609
|
const ignores = loadIgnores(dir);
|
|
5581
5610
|
const plugins = resolvePlugins(config.plugins ?? []);
|
|
5582
5611
|
const mode = opts.staged ? "staged" : "head";
|
|
5583
|
-
const
|
|
5584
|
-
const
|
|
5612
|
+
const currentFilesRaw = await getModifiedFiles(mode);
|
|
5613
|
+
const gitRoot = await getGitRoot(dir);
|
|
5614
|
+
const currentPairs = currentFilesRaw.map((raw) => ({
|
|
5615
|
+
raw,
|
|
5616
|
+
project: normalizeTouchedPathForProject(raw, gitRoot, dir)
|
|
5617
|
+
}));
|
|
5618
|
+
const inProjectPairs = currentPairs.filter((p2) => p2.project !== null);
|
|
5619
|
+
const currentFiles = inProjectPairs.map((p2) => p2.project);
|
|
5620
|
+
const outsideProjectFiles = currentPairs.filter((p2) => p2.project === null).map((p2) => p2.raw);
|
|
5621
|
+
const untrackedFiles = opts.staged ? [] : (await getUntrackedFiles()).map((f) => normalizeTouchedPathForProject(f, gitRoot, dir)).filter((f) => f !== null);
|
|
5585
5622
|
const printUntracked = () => {
|
|
5586
5623
|
if (untrackedFiles.length === 0 || jsonMode) return;
|
|
5587
5624
|
console.log("");
|
|
@@ -5596,6 +5633,7 @@ async function runGuard(opts = {}) {
|
|
|
5596
5633
|
}
|
|
5597
5634
|
const workspaceFiles = workspaceScan ? workspaceScan.changedFiles.map((c) => c.normalizedPath) : [];
|
|
5598
5635
|
const touchedFiles = [.../* @__PURE__ */ new Set([...currentFiles, ...workspaceFiles])];
|
|
5636
|
+
const displayTouched = [.../* @__PURE__ */ new Set([...inProjectPairs.map((p2) => p2.raw), ...workspaceFiles])];
|
|
5599
5637
|
if (touchedFiles.length === 0) {
|
|
5600
5638
|
if (workspaceScan) printWorkspaceHeader(workspaceScan);
|
|
5601
5639
|
console.log("kaddo guard: no modified files detected.");
|
|
@@ -5631,7 +5669,7 @@ async function runGuard(opts = {}) {
|
|
|
5631
5669
|
const ownerMapCI = loadOwners(dir);
|
|
5632
5670
|
const matchedDomainsCI = collectMatchedDomains(activeMatches.map((m) => m.artifact.domains));
|
|
5633
5671
|
const affectedOwnersCI = resolveAffectedOwners(matchedDomainsCI, ownerMapCI);
|
|
5634
|
-
printCIJson(dir, touchedFiles, activeMatches, alreadyIgnoredMatches.length, pluginSignals, affectedOwnersCI, workspaceScan, includeArchived);
|
|
5672
|
+
printCIJson(dir, touchedFiles, activeMatches, alreadyIgnoredMatches.length, pluginSignals, affectedOwnersCI, workspaceScan, includeArchived, inProjectPairs.map((p2) => ({ raw_path: p2.raw, project_path: p2.project })), outsideProjectFiles);
|
|
5635
5673
|
return;
|
|
5636
5674
|
}
|
|
5637
5675
|
if (result.silenced && pluginSignals.length === 0) {
|
|
@@ -5640,7 +5678,7 @@ async function runGuard(opts = {}) {
|
|
|
5640
5678
|
}
|
|
5641
5679
|
if (result.matches.length === 0 && pluginSignals.length === 0) {
|
|
5642
5680
|
if (workspaceScan) printWorkspaceHeader(workspaceScan);
|
|
5643
|
-
printHeader(
|
|
5681
|
+
printHeader(displayTouched);
|
|
5644
5682
|
printOwnershipScope(includeArchived);
|
|
5645
5683
|
console.log(" No artifact ownership matches found.");
|
|
5646
5684
|
console.log("");
|
|
@@ -5651,7 +5689,7 @@ async function runGuard(opts = {}) {
|
|
|
5651
5689
|
return;
|
|
5652
5690
|
}
|
|
5653
5691
|
if (workspaceScan) printWorkspaceHeader(workspaceScan);
|
|
5654
|
-
printHeader(
|
|
5692
|
+
printHeader(displayTouched);
|
|
5655
5693
|
printOwnershipScope(includeArchived);
|
|
5656
5694
|
printUntracked();
|
|
5657
5695
|
for (const match of activeMatches) {
|
|
@@ -5999,7 +6037,7 @@ function sectionParagraph(md, title) {
|
|
|
5999
6037
|
}
|
|
6000
6038
|
return "";
|
|
6001
6039
|
}
|
|
6002
|
-
function parseCapsule(id,
|
|
6040
|
+
function parseCapsule(id, path6, md) {
|
|
6003
6041
|
const { data } = matter2(md);
|
|
6004
6042
|
const updatedAt = data.updated_at ? String(data.updated_at) : void 0;
|
|
6005
6043
|
let ageDays = null;
|
|
@@ -6009,7 +6047,7 @@ function parseCapsule(id, path5, md) {
|
|
|
6009
6047
|
}
|
|
6010
6048
|
return {
|
|
6011
6049
|
id,
|
|
6012
|
-
path:
|
|
6050
|
+
path: path6,
|
|
6013
6051
|
system: data.system ? String(data.system) : id,
|
|
6014
6052
|
owner: data.owner ? String(data.owner) : void 0,
|
|
6015
6053
|
updatedAt,
|
|
@@ -6064,14 +6102,14 @@ var ALL_STATUSES = ["draft", "ready", "in-progress", "blocked", "completed"];
|
|
|
6064
6102
|
function scopeStatuses(scope) {
|
|
6065
6103
|
return scope === "all" ? { included: ALL_STATUSES, excluded: ["archived"] } : { included: ACTIVE_STATUSES, excluded: ["completed", "archived"] };
|
|
6066
6104
|
}
|
|
6067
|
-
function
|
|
6105
|
+
function toPosix3(p2) {
|
|
6068
6106
|
return p2.replace(/\\/g, "/");
|
|
6069
6107
|
}
|
|
6070
6108
|
function slug(s) {
|
|
6071
6109
|
return s.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
6072
6110
|
}
|
|
6073
6111
|
function isAdr(a) {
|
|
6074
|
-
return
|
|
6112
|
+
return toPosix3(a.filePath).includes("/tech/decisions/") && Boolean(a.type);
|
|
6075
6113
|
}
|
|
6076
6114
|
function buildGraph(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
|
|
6077
6115
|
const scope = opts.scope ?? "active";
|
|
@@ -6095,9 +6133,9 @@ function buildGraph(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
|
|
|
6095
6133
|
];
|
|
6096
6134
|
const presentLayers = [];
|
|
6097
6135
|
for (const layer2 of layerDocs) {
|
|
6098
|
-
const
|
|
6099
|
-
if (
|
|
6100
|
-
addNode({ id: layer2.id, type: layer2.type, label: layer2.label, path:
|
|
6136
|
+
const path6 = layer2.files.map((f) => `${KNOWLEDGE2}/${f}`).find((rel) => exists(join(dir, rel)));
|
|
6137
|
+
if (path6) {
|
|
6138
|
+
addNode({ id: layer2.id, type: layer2.type, label: layer2.label, path: path6 });
|
|
6101
6139
|
presentLayers.push(layer2.id);
|
|
6102
6140
|
}
|
|
6103
6141
|
}
|
|
@@ -6256,14 +6294,14 @@ function loadGraphSummary(dir) {
|
|
|
6256
6294
|
|
|
6257
6295
|
// src/core/graph-hints.ts
|
|
6258
6296
|
var KNOWLEDGE3 = "knowledge";
|
|
6259
|
-
function
|
|
6297
|
+
function toPosix4(p2) {
|
|
6260
6298
|
return p2.replace(/\\/g, "/");
|
|
6261
6299
|
}
|
|
6262
6300
|
function slug2(s) {
|
|
6263
6301
|
return s.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
6264
6302
|
}
|
|
6265
6303
|
function isAdr2(a) {
|
|
6266
|
-
return
|
|
6304
|
+
return toPosix4(a.filePath).includes("/tech/decisions/") && Boolean(a.type);
|
|
6267
6305
|
}
|
|
6268
6306
|
function capabilityHeadings(dir) {
|
|
6269
6307
|
const p2 = join(dir, KNOWLEDGE3, "product", "capabilities.md");
|
|
@@ -8007,7 +8045,7 @@ function runUnderstand() {
|
|
|
8007
8045
|
}
|
|
8008
8046
|
|
|
8009
8047
|
// src/core/classifier.ts
|
|
8010
|
-
import
|
|
8048
|
+
import path5 from "path";
|
|
8011
8049
|
var MIGRATION_PATTERNS = [
|
|
8012
8050
|
/supabase\/migrations\//,
|
|
8013
8051
|
/prisma\/migrations\//,
|
|
@@ -8046,7 +8084,7 @@ var DEP_PATTERNS = [
|
|
|
8046
8084
|
/^pom\.xml$/
|
|
8047
8085
|
];
|
|
8048
8086
|
function normalizePath2(p2) {
|
|
8049
|
-
return p2.split(
|
|
8087
|
+
return p2.split(path5.sep).join("/");
|
|
8050
8088
|
}
|
|
8051
8089
|
function matchesAny(file, patterns) {
|
|
8052
8090
|
const normalized = normalizePath2(file);
|
|
@@ -8878,10 +8916,10 @@ ${globs.map((g) => ` - ${g}`).join("\n")}`);
|
|
|
8878
8916
|
import { parse as parseYaml13, stringify as stringifyYaml5 } from "yaml";
|
|
8879
8917
|
var DESCRIPTOR_PATH2 = "knowledge/module.yml";
|
|
8880
8918
|
function readDescriptor(dir) {
|
|
8881
|
-
const
|
|
8882
|
-
if (!exists(
|
|
8919
|
+
const path6 = join(dir, DESCRIPTOR_PATH2);
|
|
8920
|
+
if (!exists(path6)) return null;
|
|
8883
8921
|
try {
|
|
8884
|
-
return parseYaml13(readFile(
|
|
8922
|
+
return parseYaml13(readFile(path6));
|
|
8885
8923
|
} catch {
|
|
8886
8924
|
return null;
|
|
8887
8925
|
}
|
|
@@ -10486,10 +10524,10 @@ function slugify3(name) {
|
|
|
10486
10524
|
return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
10487
10525
|
}
|
|
10488
10526
|
function readModulesDescriptor(dir) {
|
|
10489
|
-
const
|
|
10490
|
-
if (!exists(
|
|
10527
|
+
const path6 = join(dir, DESCRIPTOR_PATH3);
|
|
10528
|
+
if (!exists(path6)) return { version: 1, modules: [] };
|
|
10491
10529
|
try {
|
|
10492
|
-
const parsed = parseYaml14(readFile(
|
|
10530
|
+
const parsed = parseYaml14(readFile(path6));
|
|
10493
10531
|
return { version: parsed.version ?? 1, modules: parsed.modules ?? [] };
|
|
10494
10532
|
} catch {
|
|
10495
10533
|
return { version: 1, modules: [] };
|
|
@@ -10878,6 +10916,315 @@ function runGraphExport(opts = {}) {
|
|
|
10878
10916
|
outro2("Knowledge graph ready.");
|
|
10879
10917
|
}
|
|
10880
10918
|
|
|
10919
|
+
// src/core/impact-report.ts
|
|
10920
|
+
import matter8 from "gray-matter";
|
|
10921
|
+
var BROAD_GLOB = /^[^/*]+\/\*\*$/;
|
|
10922
|
+
function levelFromRatio(r) {
|
|
10923
|
+
if (r >= 0.9) return "Very High";
|
|
10924
|
+
if (r >= 0.7) return "High";
|
|
10925
|
+
if (r >= 0.4) return "Medium";
|
|
10926
|
+
return "Low";
|
|
10927
|
+
}
|
|
10928
|
+
function hasSection(body, re) {
|
|
10929
|
+
return body.split(/\r?\n/).some((l) => /^#{1,6}\s+/.test(l) && re.test(l));
|
|
10930
|
+
}
|
|
10931
|
+
function buildImpactReport(dir, opts = {}, now = /* @__PURE__ */ new Date()) {
|
|
10932
|
+
const exp = buildProjectExplanation(dir);
|
|
10933
|
+
const wis = discoverWorkItems(dir);
|
|
10934
|
+
const total = wis.length;
|
|
10935
|
+
let withOwnership = 0;
|
|
10936
|
+
let withSource = 0;
|
|
10937
|
+
let withInitiative = 0;
|
|
10938
|
+
let withLevel = 0;
|
|
10939
|
+
let withAcceptance = 0;
|
|
10940
|
+
let withDoD = 0;
|
|
10941
|
+
let connectedRoadmap = 0;
|
|
10942
|
+
const globCounts = /* @__PURE__ */ new Map();
|
|
10943
|
+
for (const wi of wis) {
|
|
10944
|
+
if (wi.codeGlobs.length > 0) withOwnership++;
|
|
10945
|
+
const hasSource = Boolean(wi.sourceId) || wi.source === "roadmap";
|
|
10946
|
+
if (hasSource) withSource++;
|
|
10947
|
+
if (hasSource || wi.initiative) connectedRoadmap++;
|
|
10948
|
+
if (wi.initiative) withInitiative++;
|
|
10949
|
+
if (wi.knowledgeLevel) withLevel++;
|
|
10950
|
+
for (const g2 of wi.codeGlobs) globCounts.set(g2, (globCounts.get(g2) ?? 0) + 1);
|
|
10951
|
+
try {
|
|
10952
|
+
const body = matter8(readFile(wi.filePath)).content;
|
|
10953
|
+
if (hasSection(body, /acceptance/i)) withAcceptance++;
|
|
10954
|
+
if (hasSection(body, /definition of done|^#{1,6}\s*done\b/i)) withDoD++;
|
|
10955
|
+
} catch {
|
|
10956
|
+
}
|
|
10957
|
+
}
|
|
10958
|
+
const ownedCodePaths = [...globCounts.values()].reduce((a, b) => a + b, 0);
|
|
10959
|
+
const broadGlobs = [...globCounts.keys()].filter((g2) => BROAD_GLOB.test(g2)).length;
|
|
10960
|
+
const ownershipOverlaps = [...globCounts.values()].filter((n) => n > 1).length;
|
|
10961
|
+
let g;
|
|
10962
|
+
if (opts.scope) {
|
|
10963
|
+
const config = loadConfig(dir);
|
|
10964
|
+
if (config) {
|
|
10965
|
+
const graph = buildGraph(dir, config, { scope: opts.scope }, now);
|
|
10966
|
+
const hints = buildGraphHints(dir, graph, now);
|
|
10967
|
+
g = { available: true, scope: graph.scope, scopeReason: graph.scope_reason, nodes: graph.nodes.length, edges: graph.edges.length, quality: hints.quality, hints: hints.summary.hints, generatedAt: graph.generated_at };
|
|
10968
|
+
} else {
|
|
10969
|
+
g = { available: false, scope: opts.scope, scopeReason: "", nodes: 0, edges: 0, quality: "unknown", hints: 0, generatedAt: "" };
|
|
10970
|
+
}
|
|
10971
|
+
} else if (exp.graph) {
|
|
10972
|
+
g = { available: true, scope: exp.graph.scope, scopeReason: exp.graph.scopeReason, nodes: exp.graph.nodes, edges: exp.graph.edges, quality: exp.graphHints?.quality ?? "unknown", hints: exp.graphHints?.totalHints ?? 0, generatedAt: exp.graph.generatedAt };
|
|
10973
|
+
} else {
|
|
10974
|
+
g = { available: false, scope: "n/a", scopeReason: "", nodes: 0, edges: 0, quality: "unknown", hints: 0, generatedAt: "" };
|
|
10975
|
+
}
|
|
10976
|
+
const layerStatus = (name) => exp.layers.find((l) => l.layer === name)?.status ?? "Missing";
|
|
10977
|
+
const byState = exp.workItems.byState;
|
|
10978
|
+
const skills = discoverInstalledSkills(dir);
|
|
10979
|
+
const knowledge_health = {
|
|
10980
|
+
Business: layerStatus("Business"),
|
|
10981
|
+
Product: layerStatus("Product"),
|
|
10982
|
+
Tech: layerStatus("Tech"),
|
|
10983
|
+
Delivery: layerStatus("Delivery"),
|
|
10984
|
+
Inventory: exp.knowledge.hasInventory ? "available" : "missing",
|
|
10985
|
+
"Context pack": exp.knowledge.hasContextPack ? "available" : "missing",
|
|
10986
|
+
Agents: exp.knowledge.hasAgents ? "available" : "missing",
|
|
10987
|
+
Skills: skills.length > 0 ? "available" : "missing"
|
|
10988
|
+
};
|
|
10989
|
+
const knowledge_coverage = [
|
|
10990
|
+
{ label: "Work Items with ownership", have: withOwnership, total },
|
|
10991
|
+
{ label: "Work Items with source", have: withSource, total },
|
|
10992
|
+
{ label: "Work Items with initiative", have: withInitiative, total },
|
|
10993
|
+
{ label: "Work Items with acceptance criteria", have: withAcceptance, total },
|
|
10994
|
+
{ label: "Work Items with Definition of Done", have: withDoD, total },
|
|
10995
|
+
{ label: "Work Items with knowledge level", have: withLevel, total }
|
|
10996
|
+
];
|
|
10997
|
+
const coverageRatio = (have) => total > 0 ? have / total : 0;
|
|
10998
|
+
const deliveryTraceable = ["Traceable", "Structured", "Consolidated"].includes(layerStatus("Delivery"));
|
|
10999
|
+
const ctxReasons = [];
|
|
11000
|
+
let ctxScore = 0;
|
|
11001
|
+
if (["Consolidated", "Structured"].includes(layerStatus("Business"))) {
|
|
11002
|
+
ctxScore++;
|
|
11003
|
+
ctxReasons.push("Business knowledge is present.");
|
|
11004
|
+
}
|
|
11005
|
+
if (["Consolidated", "Structured"].includes(layerStatus("Product"))) {
|
|
11006
|
+
ctxScore++;
|
|
11007
|
+
ctxReasons.push("Product knowledge is present.");
|
|
11008
|
+
}
|
|
11009
|
+
if (layerStatus("Tech") !== "Missing") {
|
|
11010
|
+
ctxScore++;
|
|
11011
|
+
ctxReasons.push("Tech knowledge is structured.");
|
|
11012
|
+
}
|
|
11013
|
+
if (deliveryTraceable) {
|
|
11014
|
+
ctxScore++;
|
|
11015
|
+
ctxReasons.push("Delivery is traceable.");
|
|
11016
|
+
}
|
|
11017
|
+
if (exp.knowledge.hasContextPack) {
|
|
11018
|
+
ctxScore++;
|
|
11019
|
+
ctxReasons.push("Context pack is available.");
|
|
11020
|
+
}
|
|
11021
|
+
if (g.available && g.quality !== "empty") {
|
|
11022
|
+
ctxScore++;
|
|
11023
|
+
ctxReasons.push(`Graph quality is ${g.quality}.`);
|
|
11024
|
+
}
|
|
11025
|
+
const ctxLevel = ctxScore >= 6 ? "Very High" : ctxScore >= 4 ? "High" : ctxScore >= 2 ? "Medium" : "Low";
|
|
11026
|
+
const ambiguityRatio = total > 0 ? (withAcceptance + withDoD + withSource) / (3 * total) : 0;
|
|
11027
|
+
const aiReady = exp.knowledge.hasContextPack && g.available && g.quality !== "empty" && skills.length > 0 && deliveryTraceable ? "High" : exp.knowledge.hasContextPack && g.available && g.quality !== "empty" ? "Medium" : "Low";
|
|
11028
|
+
const onboarding = levelFromRatio(
|
|
11029
|
+
[layerStatus("Business"), layerStatus("Product"), layerStatus("Tech"), layerStatus("Delivery")].filter((s) => s !== "Missing").length / 4 * (exp.knowledge.hasContextPack ? 1 : 0.6)
|
|
11030
|
+
);
|
|
11031
|
+
const deliveryTrace = levelFromRatio(
|
|
11032
|
+
coverageRatio(withOwnership) * 0.5 + (g.available && g.edges > 0 ? 1 : 0) * 0.25 + (exp.roadmap.present ? 1 : 0) * 0.25
|
|
11033
|
+
);
|
|
11034
|
+
const impact_signals = {
|
|
11035
|
+
ambiguity_reduction: levelFromRatio(ambiguityRatio),
|
|
11036
|
+
drift_prevention: withOwnership > 0 ? "Active" : "Limited",
|
|
11037
|
+
onboarding_support: onboarding,
|
|
11038
|
+
delivery_traceability: deliveryTrace,
|
|
11039
|
+
ai_context_readiness: aiReady,
|
|
11040
|
+
maintenance_readiness: levelFromRatio((coverageRatio(withOwnership) + (deliveryTraceable ? 1 : 0)) / 2)
|
|
11041
|
+
};
|
|
11042
|
+
let score = null;
|
|
11043
|
+
if (total > 0) {
|
|
11044
|
+
const healthPts = [layerStatus("Business"), layerStatus("Product"), layerStatus("Tech"), layerStatus("Delivery")].filter((s) => s !== "Missing").length / 4 * 20;
|
|
11045
|
+
const covAvg = knowledge_coverage.reduce((a, c) => a + coverageRatio(c.have), 0) / knowledge_coverage.length;
|
|
11046
|
+
const coveragePts = covAvg * 20;
|
|
11047
|
+
const ownPts = coverageRatio(withOwnership) * 15;
|
|
11048
|
+
const traceBlend = (exp.roadmap.candidates > 0 ? exp.roadmap.materialized / exp.roadmap.candidates : 1) * 0.5 + coverageRatio(withOwnership) * 0.5;
|
|
11049
|
+
const tracePts = traceBlend * 20;
|
|
11050
|
+
const qualityMap = { good: 1, partial: 0.6, sparse: 0.3, empty: 0, unknown: 0 };
|
|
11051
|
+
const graphPts = (g.available ? qualityMap[g.quality] ?? 0 : 0) * 15;
|
|
11052
|
+
const ctxPts = { Low: 0.25, Medium: 0.5, High: 0.8, "Very High": 1 }[ctxLevel] * 10;
|
|
11053
|
+
score = Math.round(healthPts + coveragePts + ownPts + tracePts + graphPts + ctxPts);
|
|
11054
|
+
}
|
|
11055
|
+
const actions = [];
|
|
11056
|
+
if (!g.available) actions.push("Run `kaddo graph export --scope all` to inspect full traceability.");
|
|
11057
|
+
else if (g.scope === "active" && g.quality === "empty") actions.push("Run `kaddo graph export --scope all` to include completed Work Items.");
|
|
11058
|
+
if (exp.roadmap.remaining > 0) actions.push(`Materialize the ${exp.roadmap.remaining} remaining roadmap candidate(s) with \`kaddo create --from roadmap\`.`);
|
|
11059
|
+
if (total > 0 && withOwnership < total) actions.push("Add code ownership to Work Items without `code:` globs (`kaddo owners suggest`).");
|
|
11060
|
+
if (total > 0 && withInitiative < total) actions.push("Add an initiative to Work Items missing one.");
|
|
11061
|
+
if (g.available && g.hints > 0) actions.push("Review graph hints (`kaddo://graph-hints` or `.kaddo/graph-hints.md`).");
|
|
11062
|
+
if (skills.length === 0) actions.push("Install reusable skills with `kaddo add skills`.");
|
|
11063
|
+
actions.push("Run `kaddo guard` before committing to catch knowledge drift.");
|
|
11064
|
+
const summary = [];
|
|
11065
|
+
if (total > 0) summary.push(`${withOwnership}/${total} Work Items include code ownership.`);
|
|
11066
|
+
if (g.available) summary.push(`The knowledge graph is ${g.quality} with ${g.nodes} nodes and ${g.edges} edges (${g.scope} scope).`);
|
|
11067
|
+
else summary.push("The knowledge graph has not been exported yet.");
|
|
11068
|
+
if (g.available) summary.push(g.hints === 0 ? "No graph hints are currently open." : `${g.hints} graph hint(s) are open.`);
|
|
11069
|
+
if (exp.roadmap.present) summary.push(`Delivery: ${exp.roadmap.materialized} materialized, ${exp.roadmap.remaining} roadmap candidate(s) remaining.`);
|
|
11070
|
+
summary.push(`Context readiness: ${ctxLevel}.`);
|
|
11071
|
+
return {
|
|
11072
|
+
generated_at: now.toISOString(),
|
|
11073
|
+
project: exp.project.name,
|
|
11074
|
+
scope: g.scope,
|
|
11075
|
+
executive_summary: summary,
|
|
11076
|
+
knowledge_health,
|
|
11077
|
+
knowledge_coverage,
|
|
11078
|
+
ownership_coverage: {
|
|
11079
|
+
coverage_percent: total > 0 ? Math.round(withOwnership / total * 100) : 0,
|
|
11080
|
+
work_items_with_ownership: `${withOwnership}/${total}`,
|
|
11081
|
+
owned_code_paths: ownedCodePaths,
|
|
11082
|
+
broad_globs: broadGlobs,
|
|
11083
|
+
ownership_overlaps: ownershipOverlaps
|
|
11084
|
+
},
|
|
11085
|
+
traceability: {
|
|
11086
|
+
roadmap_candidates: exp.roadmap.candidates,
|
|
11087
|
+
materialized_work_items: exp.roadmap.materialized,
|
|
11088
|
+
remaining_candidates: exp.roadmap.remaining,
|
|
11089
|
+
completed_work_items: byState.completed,
|
|
11090
|
+
work_items_connected_to_roadmap: `${connectedRoadmap}/${total}`,
|
|
11091
|
+
work_items_connected_to_code: `${withOwnership}/${total}`,
|
|
11092
|
+
graph_quality: g.available ? g.quality : "not available",
|
|
11093
|
+
graph_nodes: g.available ? g.nodes : null,
|
|
11094
|
+
graph_edges: g.available ? g.edges : null,
|
|
11095
|
+
graph_hints: g.available ? g.hints : null
|
|
11096
|
+
},
|
|
11097
|
+
context_readiness: { level: ctxLevel, reasons: ctxReasons },
|
|
11098
|
+
work_item_readiness: {
|
|
11099
|
+
draft: byState.draft,
|
|
11100
|
+
ready: byState.ready,
|
|
11101
|
+
in_progress: byState["in-progress"],
|
|
11102
|
+
blocked: byState.blocked,
|
|
11103
|
+
completed: byState.completed,
|
|
11104
|
+
ready_quality: byState.ready > 0 ? `${byState.ready} ready` : "not applicable"
|
|
11105
|
+
},
|
|
11106
|
+
graph_quality: g.available ? { available: true, scope: g.scope, quality: g.quality, nodes: g.nodes, edges: g.edges, hints: g.hints, reason: g.scopeReason, last_exported: g.generatedAt } : { available: false, suggestion: "Run `kaddo graph export --scope all`." },
|
|
11107
|
+
guard_activity: {
|
|
11108
|
+
available: false,
|
|
11109
|
+
note: "Guard history is not persisted. Run `kaddo guard` to check drift; future versions may store runs for trend analysis."
|
|
11110
|
+
},
|
|
11111
|
+
impact_signals,
|
|
11112
|
+
score,
|
|
11113
|
+
suggested_actions: actions
|
|
11114
|
+
};
|
|
11115
|
+
}
|
|
11116
|
+
function ratio(have, total) {
|
|
11117
|
+
return `${have}/${total}`;
|
|
11118
|
+
}
|
|
11119
|
+
function renderImpactMarkdown(r) {
|
|
11120
|
+
const L = [];
|
|
11121
|
+
L.push("# Kaddo Knowledge Impact Report", "");
|
|
11122
|
+
L.push(`Generated at: ${r.generated_at}`);
|
|
11123
|
+
L.push(`Project: ${r.project}`);
|
|
11124
|
+
L.push(`Scope: ${r.scope}`);
|
|
11125
|
+
if (r.score !== null) L.push(`Knowledge Impact Score: ${r.score}/100`);
|
|
11126
|
+
else L.push("Knowledge Impact Score: not available");
|
|
11127
|
+
L.push("");
|
|
11128
|
+
L.push("## Executive Summary", "");
|
|
11129
|
+
for (const s2 of r.executive_summary) L.push(`- ${s2}`);
|
|
11130
|
+
L.push("");
|
|
11131
|
+
L.push("## Knowledge Health", "");
|
|
11132
|
+
for (const [k, v] of Object.entries(r.knowledge_health)) L.push(`- ${k}: ${v}`);
|
|
11133
|
+
L.push("");
|
|
11134
|
+
L.push("## Knowledge Coverage", "");
|
|
11135
|
+
for (const c of r.knowledge_coverage) L.push(`- ${c.label}: ${ratio(c.have, c.total)}`);
|
|
11136
|
+
L.push("");
|
|
11137
|
+
L.push("## Ownership Coverage", "");
|
|
11138
|
+
L.push(`- Coverage: ${r.ownership_coverage.coverage_percent}%`);
|
|
11139
|
+
L.push(`- Work Items with code ownership: ${r.ownership_coverage.work_items_with_ownership}`);
|
|
11140
|
+
L.push(`- Owned code paths: ${r.ownership_coverage.owned_code_paths}`);
|
|
11141
|
+
L.push(`- Broad globs detected: ${r.ownership_coverage.broad_globs}`);
|
|
11142
|
+
L.push(`- Ownership overlaps: ${r.ownership_coverage.ownership_overlaps}`);
|
|
11143
|
+
L.push("");
|
|
11144
|
+
L.push("## Traceability", "");
|
|
11145
|
+
const t = r.traceability;
|
|
11146
|
+
L.push(`- Roadmap candidates: ${t.roadmap_candidates}`);
|
|
11147
|
+
L.push(`- Materialized Work Items: ${t.materialized_work_items}`);
|
|
11148
|
+
L.push(`- Remaining candidates: ${t.remaining_candidates}`);
|
|
11149
|
+
L.push(`- Completed Work Items: ${t.completed_work_items}`);
|
|
11150
|
+
L.push(`- Work Items connected to roadmap: ${t.work_items_connected_to_roadmap}`);
|
|
11151
|
+
L.push(`- Work Items connected to code: ${t.work_items_connected_to_code}`);
|
|
11152
|
+
L.push(`- Graph quality: ${t.graph_quality}`);
|
|
11153
|
+
if (t.graph_nodes !== null) L.push(`- Graph nodes: ${t.graph_nodes}`);
|
|
11154
|
+
if (t.graph_edges !== null) L.push(`- Graph edges: ${t.graph_edges}`);
|
|
11155
|
+
if (t.graph_hints !== null) L.push(`- Graph hints: ${t.graph_hints}`);
|
|
11156
|
+
L.push("");
|
|
11157
|
+
L.push("## Context Readiness", "");
|
|
11158
|
+
L.push(`Context Readiness: ${r.context_readiness.level}`);
|
|
11159
|
+
if (r.context_readiness.reasons.length > 0) {
|
|
11160
|
+
L.push("", "Reason:");
|
|
11161
|
+
for (const reason of r.context_readiness.reasons) L.push(`- ${reason}`);
|
|
11162
|
+
}
|
|
11163
|
+
L.push("");
|
|
11164
|
+
L.push("## Work Item Readiness", "");
|
|
11165
|
+
const w = r.work_item_readiness;
|
|
11166
|
+
L.push(`- Draft: ${w.draft}`);
|
|
11167
|
+
L.push(`- Ready: ${w.ready}`);
|
|
11168
|
+
L.push(`- In Progress: ${w.in_progress}`);
|
|
11169
|
+
L.push(`- Blocked: ${w.blocked}`);
|
|
11170
|
+
L.push(`- Completed: ${w.completed}`);
|
|
11171
|
+
L.push(`- Ready quality: ${w.ready_quality}`);
|
|
11172
|
+
L.push("");
|
|
11173
|
+
L.push("## Graph Quality", "");
|
|
11174
|
+
if (r.graph_quality.available) {
|
|
11175
|
+
const gq = r.graph_quality;
|
|
11176
|
+
L.push(`- Scope: ${gq.scope}`);
|
|
11177
|
+
L.push(`- Quality: ${gq.quality}`);
|
|
11178
|
+
L.push(`- Nodes: ${gq.nodes}`);
|
|
11179
|
+
L.push(`- Edges: ${gq.edges}`);
|
|
11180
|
+
L.push(`- Hints: ${gq.hints}`);
|
|
11181
|
+
if (gq.reason) L.push(`- Reason: ${gq.reason}`);
|
|
11182
|
+
if (gq.last_exported) L.push(`- Last exported: ${gq.last_exported}`);
|
|
11183
|
+
} else {
|
|
11184
|
+
L.push("- Graph data not available.");
|
|
11185
|
+
L.push(`- Tip: ${r.graph_quality.suggestion}`);
|
|
11186
|
+
}
|
|
11187
|
+
L.push("");
|
|
11188
|
+
L.push("## Guard Activity", "");
|
|
11189
|
+
L.push("- Guard history: not available");
|
|
11190
|
+
L.push(`- Note: ${r.guard_activity.note}`);
|
|
11191
|
+
L.push("");
|
|
11192
|
+
L.push("## Impact Signals", "");
|
|
11193
|
+
const s = r.impact_signals;
|
|
11194
|
+
L.push(`- Ambiguity reduction: ${s.ambiguity_reduction}`);
|
|
11195
|
+
L.push(`- Drift prevention: ${s.drift_prevention}`);
|
|
11196
|
+
L.push(`- Onboarding support: ${s.onboarding_support}`);
|
|
11197
|
+
L.push(`- Delivery traceability: ${s.delivery_traceability}`);
|
|
11198
|
+
L.push(`- AI context readiness: ${s.ai_context_readiness}`);
|
|
11199
|
+
L.push(`- Maintenance readiness: ${s.maintenance_readiness}`);
|
|
11200
|
+
L.push("");
|
|
11201
|
+
L.push("## Suggested Actions", "");
|
|
11202
|
+
r.suggested_actions.forEach((a, i) => L.push(`${i + 1}. ${a}`));
|
|
11203
|
+
L.push("");
|
|
11204
|
+
return L.join("\n");
|
|
11205
|
+
}
|
|
11206
|
+
function serializeImpactJson(r) {
|
|
11207
|
+
return JSON.stringify(r, null, 2) + "\n";
|
|
11208
|
+
}
|
|
11209
|
+
|
|
11210
|
+
// src/commands/report.ts
|
|
11211
|
+
function runReportImpact(opts = {}) {
|
|
11212
|
+
const dir = cwd();
|
|
11213
|
+
requireConfig(dir);
|
|
11214
|
+
const report = buildImpactReport(dir);
|
|
11215
|
+
const content = opts.json ? serializeImpactJson(report) : renderImpactMarkdown(report);
|
|
11216
|
+
if (opts.output) {
|
|
11217
|
+
intro2("kaddo report impact");
|
|
11218
|
+
const rel = opts.output;
|
|
11219
|
+
writeFile(join(dir, rel), content);
|
|
11220
|
+
log2.success(`Wrote ${rel.replace(/\\/g, "/")}`);
|
|
11221
|
+
printCommandFooter("report impact");
|
|
11222
|
+
outro2("Impact report written.");
|
|
11223
|
+
return;
|
|
11224
|
+
}
|
|
11225
|
+
console.log(content);
|
|
11226
|
+
}
|
|
11227
|
+
|
|
10881
11228
|
// src/index.ts
|
|
10882
11229
|
var require2 = createRequire(import.meta.url);
|
|
10883
11230
|
var { version } = require2("../package.json");
|
|
@@ -10899,13 +11246,20 @@ var capsuleCmd = program.command("capsule").description("Export this project as
|
|
|
10899
11246
|
capsuleCmd.command("export").description("Write a Knowledge Capsule about this project to .kaddo/exports/").action(() => {
|
|
10900
11247
|
runCapsuleExport();
|
|
10901
11248
|
});
|
|
10902
|
-
capsuleCmd.command("add <path>").description("Register an external Knowledge Capsule as project context (.kaddo/external.yml)").action((
|
|
10903
|
-
runCapsuleAdd(
|
|
11249
|
+
capsuleCmd.command("add <path>").description("Register an external Knowledge Capsule as project context (.kaddo/external.yml)").action((path6) => {
|
|
11250
|
+
runCapsuleAdd(path6);
|
|
10904
11251
|
});
|
|
10905
11252
|
var graphCmd = program.command("graph").description("Export the lightweight, file-based knowledge graph of the project");
|
|
10906
11253
|
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) => {
|
|
10907
11254
|
runGraphExport(opts);
|
|
10908
11255
|
});
|
|
11256
|
+
var reportCmd = program.command("report").description("Generate Kaddo reports");
|
|
11257
|
+
reportCmd.command("impact").description("Knowledge Impact Report: knowledge health, coverage, traceability, readiness (deterministic, no LLM)").option("--json", "Output JSON instead of Markdown").option("--output <path>", "Write the report to a file (e.g. .kaddo/reports/impact-report.md)").action((opts) => {
|
|
11258
|
+
runReportImpact(opts);
|
|
11259
|
+
});
|
|
11260
|
+
program.command("impact").description("Alias for `kaddo report impact`").option("--json", "Output JSON instead of Markdown").option("--output <path>", "Write the report to a file").action((opts) => {
|
|
11261
|
+
runReportImpact(opts);
|
|
11262
|
+
});
|
|
10909
11263
|
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) => {
|
|
10910
11264
|
await runGuard(opts);
|
|
10911
11265
|
});
|