@kaddo/cli 3.22.0 → 3.22.1
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 -39
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -509,6 +509,7 @@ 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 |
|
|
512
513
|
|
|
513
514
|
**Optional modules (installed with `kaddo add`):**
|
|
514
515
|
|
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}`);
|
|
@@ -4902,6 +4902,14 @@ async function isGitRepo(dir) {
|
|
|
4902
4902
|
return false;
|
|
4903
4903
|
}
|
|
4904
4904
|
}
|
|
4905
|
+
async function getGitRoot(dir) {
|
|
4906
|
+
try {
|
|
4907
|
+
const result = await execa("git", ["rev-parse", "--show-toplevel"], { cwd: dir });
|
|
4908
|
+
return result.stdout.trim() || null;
|
|
4909
|
+
} catch {
|
|
4910
|
+
return null;
|
|
4911
|
+
}
|
|
4912
|
+
}
|
|
4905
4913
|
async function getUntrackedFiles() {
|
|
4906
4914
|
try {
|
|
4907
4915
|
const result = await execa("git", ["ls-files", "--others", "--exclude-standard"]);
|
|
@@ -5048,6 +5056,20 @@ function discoverWorkItems(dir) {
|
|
|
5048
5056
|
|
|
5049
5057
|
// src/core/diff-analysis.ts
|
|
5050
5058
|
import { minimatch } from "minimatch";
|
|
5059
|
+
import path3 from "path";
|
|
5060
|
+
function toPosix2(p2) {
|
|
5061
|
+
return p2.replace(/\\/g, "/");
|
|
5062
|
+
}
|
|
5063
|
+
function normalizeTouchedPathForProject(touchedPath, gitRoot, projectRoot) {
|
|
5064
|
+
const t = toPosix2(touchedPath).replace(/^\.\//, "").replace(/^\/+/, "");
|
|
5065
|
+
if (!gitRoot) return t;
|
|
5066
|
+
const prefix = toPosix2(path3.relative(gitRoot, projectRoot)).replace(/^\.\//, "").replace(/\/+$/, "");
|
|
5067
|
+
if (prefix === "" || prefix === ".") return t;
|
|
5068
|
+
if (prefix.startsWith("..")) return t;
|
|
5069
|
+
if (t === prefix) return null;
|
|
5070
|
+
if (t.startsWith(prefix + "/")) return t.slice(prefix.length + 1);
|
|
5071
|
+
return null;
|
|
5072
|
+
}
|
|
5051
5073
|
function normalizePath(p2) {
|
|
5052
5074
|
return p2.replace(/\\/g, "/");
|
|
5053
5075
|
}
|
|
@@ -5107,10 +5129,10 @@ function analyzeGuard(touchedFiles, artifacts, silentWithoutOwnership) {
|
|
|
5107
5129
|
import { parse as parseYaml4, stringify as stringifyYaml2 } from "yaml";
|
|
5108
5130
|
var IGNORE_FILE = ".kaddo/ignores.yml";
|
|
5109
5131
|
function loadIgnores(dir) {
|
|
5110
|
-
const
|
|
5111
|
-
if (!exists(
|
|
5132
|
+
const path6 = join(dir, IGNORE_FILE);
|
|
5133
|
+
if (!exists(path6)) return [];
|
|
5112
5134
|
try {
|
|
5113
|
-
const raw = readFile(
|
|
5135
|
+
const raw = readFile(path6);
|
|
5114
5136
|
const parsed = parseYaml4(raw);
|
|
5115
5137
|
return Array.isArray(parsed) ? parsed : [];
|
|
5116
5138
|
} catch {
|
|
@@ -5118,7 +5140,7 @@ function loadIgnores(dir) {
|
|
|
5118
5140
|
}
|
|
5119
5141
|
}
|
|
5120
5142
|
function saveIgnore(dir, entry) {
|
|
5121
|
-
const
|
|
5143
|
+
const path6 = join(dir, IGNORE_FILE);
|
|
5122
5144
|
const existing = loadIgnores(dir);
|
|
5123
5145
|
const idx = existing.findIndex((e) => e.artifact_id === entry.artifact_id);
|
|
5124
5146
|
if (idx >= 0) {
|
|
@@ -5126,17 +5148,17 @@ function saveIgnore(dir, entry) {
|
|
|
5126
5148
|
} else {
|
|
5127
5149
|
existing.push(entry);
|
|
5128
5150
|
}
|
|
5129
|
-
writeFile(
|
|
5151
|
+
writeFile(path6, stringifyYaml2(existing));
|
|
5130
5152
|
}
|
|
5131
5153
|
function isIgnored(ignores, artifactId) {
|
|
5132
5154
|
return ignores.find((e) => e.artifact_id === artifactId);
|
|
5133
5155
|
}
|
|
5134
5156
|
function removeIgnore(dir, artifactId) {
|
|
5135
|
-
const
|
|
5157
|
+
const path6 = join(dir, IGNORE_FILE);
|
|
5136
5158
|
const existing = loadIgnores(dir);
|
|
5137
5159
|
const filtered = existing.filter((e) => e.artifact_id !== artifactId);
|
|
5138
5160
|
if (filtered.length === existing.length) return false;
|
|
5139
|
-
writeFile(
|
|
5161
|
+
writeFile(path6, stringifyYaml2(filtered));
|
|
5140
5162
|
return true;
|
|
5141
5163
|
}
|
|
5142
5164
|
|
|
@@ -5158,11 +5180,11 @@ function moduleArtifactCoverage(dir, id) {
|
|
|
5158
5180
|
};
|
|
5159
5181
|
}
|
|
5160
5182
|
function loadMappedModules(dir) {
|
|
5161
|
-
const
|
|
5162
|
-
if (!exists(
|
|
5183
|
+
const path6 = join(dir, DESCRIPTOR_PATH);
|
|
5184
|
+
if (!exists(path6)) return [];
|
|
5163
5185
|
let parsed;
|
|
5164
5186
|
try {
|
|
5165
|
-
parsed = parseYaml5(readFile(
|
|
5187
|
+
parsed = parseYaml5(readFile(path6));
|
|
5166
5188
|
} catch {
|
|
5167
5189
|
return [];
|
|
5168
5190
|
}
|
|
@@ -5254,7 +5276,7 @@ async function collectWorkspaceChanges(dir, mode = "head", deps = defaultDeps) {
|
|
|
5254
5276
|
}
|
|
5255
5277
|
|
|
5256
5278
|
// src/commands/guard.ts
|
|
5257
|
-
import
|
|
5279
|
+
import path4 from "path";
|
|
5258
5280
|
import { parse as parseYaml7 } from "yaml";
|
|
5259
5281
|
|
|
5260
5282
|
// src/plugins/prisma.ts
|
|
@@ -5458,7 +5480,7 @@ function printHeader(touchedFiles) {
|
|
|
5458
5480
|
function artifactLabel(artifact, dir) {
|
|
5459
5481
|
if (artifact.id) return artifact.id;
|
|
5460
5482
|
if (artifact.title) return artifact.title;
|
|
5461
|
-
const rel =
|
|
5483
|
+
const rel = path4.relative(dir, artifact.filePath).split(path4.sep).join("/");
|
|
5462
5484
|
return rel || artifact.filePath;
|
|
5463
5485
|
}
|
|
5464
5486
|
function printWorkspaceHeader(scan2) {
|
|
@@ -5524,7 +5546,7 @@ async function offerIgnore(dir, match) {
|
|
|
5524
5546
|
log2.success(`${id} added to .kaddo/ignores.yml`);
|
|
5525
5547
|
return true;
|
|
5526
5548
|
}
|
|
5527
|
-
function printCIJson(dir, touchedFiles, activeMatches, ignoredCount, pluginSignals, affectedOwners, workspace, includeArchived) {
|
|
5549
|
+
function printCIJson(dir, touchedFiles, activeMatches, ignoredCount, pluginSignals, affectedOwners, workspace, includeArchived, normalizedFiles, outsideProjectFiles) {
|
|
5528
5550
|
const output = {
|
|
5529
5551
|
kaddo_guard: true,
|
|
5530
5552
|
ci: true,
|
|
@@ -5532,6 +5554,9 @@ function printCIJson(dir, touchedFiles, activeMatches, ignoredCount, pluginSigna
|
|
|
5532
5554
|
included: includeArchived ? ["draft", "ready", "in-progress", "blocked", "completed", "archived"] : ["draft", "ready", "in-progress", "blocked", "completed"],
|
|
5533
5555
|
excluded: includeArchived ? [] : ["archived"]
|
|
5534
5556
|
},
|
|
5557
|
+
// Git-root-relative → project-root-relative mapping used for matching (VS-060.1).
|
|
5558
|
+
normalized_files: normalizedFiles,
|
|
5559
|
+
files_outside_project: outsideProjectFiles,
|
|
5535
5560
|
touched_files: touchedFiles.length,
|
|
5536
5561
|
fyi_count: activeMatches.length,
|
|
5537
5562
|
ignored_count: ignoredCount,
|
|
@@ -5580,8 +5605,16 @@ async function runGuard(opts = {}) {
|
|
|
5580
5605
|
const ignores = loadIgnores(dir);
|
|
5581
5606
|
const plugins = resolvePlugins(config.plugins ?? []);
|
|
5582
5607
|
const mode = opts.staged ? "staged" : "head";
|
|
5583
|
-
const
|
|
5584
|
-
const
|
|
5608
|
+
const currentFilesRaw = await getModifiedFiles(mode);
|
|
5609
|
+
const gitRoot = await getGitRoot(dir);
|
|
5610
|
+
const currentPairs = currentFilesRaw.map((raw) => ({
|
|
5611
|
+
raw,
|
|
5612
|
+
project: normalizeTouchedPathForProject(raw, gitRoot, dir)
|
|
5613
|
+
}));
|
|
5614
|
+
const inProjectPairs = currentPairs.filter((p2) => p2.project !== null);
|
|
5615
|
+
const currentFiles = inProjectPairs.map((p2) => p2.project);
|
|
5616
|
+
const outsideProjectFiles = currentPairs.filter((p2) => p2.project === null).map((p2) => p2.raw);
|
|
5617
|
+
const untrackedFiles = opts.staged ? [] : (await getUntrackedFiles()).map((f) => normalizeTouchedPathForProject(f, gitRoot, dir)).filter((f) => f !== null);
|
|
5585
5618
|
const printUntracked = () => {
|
|
5586
5619
|
if (untrackedFiles.length === 0 || jsonMode) return;
|
|
5587
5620
|
console.log("");
|
|
@@ -5596,6 +5629,7 @@ async function runGuard(opts = {}) {
|
|
|
5596
5629
|
}
|
|
5597
5630
|
const workspaceFiles = workspaceScan ? workspaceScan.changedFiles.map((c) => c.normalizedPath) : [];
|
|
5598
5631
|
const touchedFiles = [.../* @__PURE__ */ new Set([...currentFiles, ...workspaceFiles])];
|
|
5632
|
+
const displayTouched = [.../* @__PURE__ */ new Set([...inProjectPairs.map((p2) => p2.raw), ...workspaceFiles])];
|
|
5599
5633
|
if (touchedFiles.length === 0) {
|
|
5600
5634
|
if (workspaceScan) printWorkspaceHeader(workspaceScan);
|
|
5601
5635
|
console.log("kaddo guard: no modified files detected.");
|
|
@@ -5631,7 +5665,7 @@ async function runGuard(opts = {}) {
|
|
|
5631
5665
|
const ownerMapCI = loadOwners(dir);
|
|
5632
5666
|
const matchedDomainsCI = collectMatchedDomains(activeMatches.map((m) => m.artifact.domains));
|
|
5633
5667
|
const affectedOwnersCI = resolveAffectedOwners(matchedDomainsCI, ownerMapCI);
|
|
5634
|
-
printCIJson(dir, touchedFiles, activeMatches, alreadyIgnoredMatches.length, pluginSignals, affectedOwnersCI, workspaceScan, includeArchived);
|
|
5668
|
+
printCIJson(dir, touchedFiles, activeMatches, alreadyIgnoredMatches.length, pluginSignals, affectedOwnersCI, workspaceScan, includeArchived, inProjectPairs.map((p2) => ({ raw_path: p2.raw, project_path: p2.project })), outsideProjectFiles);
|
|
5635
5669
|
return;
|
|
5636
5670
|
}
|
|
5637
5671
|
if (result.silenced && pluginSignals.length === 0) {
|
|
@@ -5640,7 +5674,7 @@ async function runGuard(opts = {}) {
|
|
|
5640
5674
|
}
|
|
5641
5675
|
if (result.matches.length === 0 && pluginSignals.length === 0) {
|
|
5642
5676
|
if (workspaceScan) printWorkspaceHeader(workspaceScan);
|
|
5643
|
-
printHeader(
|
|
5677
|
+
printHeader(displayTouched);
|
|
5644
5678
|
printOwnershipScope(includeArchived);
|
|
5645
5679
|
console.log(" No artifact ownership matches found.");
|
|
5646
5680
|
console.log("");
|
|
@@ -5651,7 +5685,7 @@ async function runGuard(opts = {}) {
|
|
|
5651
5685
|
return;
|
|
5652
5686
|
}
|
|
5653
5687
|
if (workspaceScan) printWorkspaceHeader(workspaceScan);
|
|
5654
|
-
printHeader(
|
|
5688
|
+
printHeader(displayTouched);
|
|
5655
5689
|
printOwnershipScope(includeArchived);
|
|
5656
5690
|
printUntracked();
|
|
5657
5691
|
for (const match of activeMatches) {
|
|
@@ -5999,7 +6033,7 @@ function sectionParagraph(md, title) {
|
|
|
5999
6033
|
}
|
|
6000
6034
|
return "";
|
|
6001
6035
|
}
|
|
6002
|
-
function parseCapsule(id,
|
|
6036
|
+
function parseCapsule(id, path6, md) {
|
|
6003
6037
|
const { data } = matter2(md);
|
|
6004
6038
|
const updatedAt = data.updated_at ? String(data.updated_at) : void 0;
|
|
6005
6039
|
let ageDays = null;
|
|
@@ -6009,7 +6043,7 @@ function parseCapsule(id, path5, md) {
|
|
|
6009
6043
|
}
|
|
6010
6044
|
return {
|
|
6011
6045
|
id,
|
|
6012
|
-
path:
|
|
6046
|
+
path: path6,
|
|
6013
6047
|
system: data.system ? String(data.system) : id,
|
|
6014
6048
|
owner: data.owner ? String(data.owner) : void 0,
|
|
6015
6049
|
updatedAt,
|
|
@@ -6064,14 +6098,14 @@ var ALL_STATUSES = ["draft", "ready", "in-progress", "blocked", "completed"];
|
|
|
6064
6098
|
function scopeStatuses(scope) {
|
|
6065
6099
|
return scope === "all" ? { included: ALL_STATUSES, excluded: ["archived"] } : { included: ACTIVE_STATUSES, excluded: ["completed", "archived"] };
|
|
6066
6100
|
}
|
|
6067
|
-
function
|
|
6101
|
+
function toPosix3(p2) {
|
|
6068
6102
|
return p2.replace(/\\/g, "/");
|
|
6069
6103
|
}
|
|
6070
6104
|
function slug(s) {
|
|
6071
6105
|
return s.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
6072
6106
|
}
|
|
6073
6107
|
function isAdr(a) {
|
|
6074
|
-
return
|
|
6108
|
+
return toPosix3(a.filePath).includes("/tech/decisions/") && Boolean(a.type);
|
|
6075
6109
|
}
|
|
6076
6110
|
function buildGraph(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
|
|
6077
6111
|
const scope = opts.scope ?? "active";
|
|
@@ -6095,9 +6129,9 @@ function buildGraph(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
|
|
|
6095
6129
|
];
|
|
6096
6130
|
const presentLayers = [];
|
|
6097
6131
|
for (const layer2 of layerDocs) {
|
|
6098
|
-
const
|
|
6099
|
-
if (
|
|
6100
|
-
addNode({ id: layer2.id, type: layer2.type, label: layer2.label, path:
|
|
6132
|
+
const path6 = layer2.files.map((f) => `${KNOWLEDGE2}/${f}`).find((rel) => exists(join(dir, rel)));
|
|
6133
|
+
if (path6) {
|
|
6134
|
+
addNode({ id: layer2.id, type: layer2.type, label: layer2.label, path: path6 });
|
|
6101
6135
|
presentLayers.push(layer2.id);
|
|
6102
6136
|
}
|
|
6103
6137
|
}
|
|
@@ -6256,14 +6290,14 @@ function loadGraphSummary(dir) {
|
|
|
6256
6290
|
|
|
6257
6291
|
// src/core/graph-hints.ts
|
|
6258
6292
|
var KNOWLEDGE3 = "knowledge";
|
|
6259
|
-
function
|
|
6293
|
+
function toPosix4(p2) {
|
|
6260
6294
|
return p2.replace(/\\/g, "/");
|
|
6261
6295
|
}
|
|
6262
6296
|
function slug2(s) {
|
|
6263
6297
|
return s.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
6264
6298
|
}
|
|
6265
6299
|
function isAdr2(a) {
|
|
6266
|
-
return
|
|
6300
|
+
return toPosix4(a.filePath).includes("/tech/decisions/") && Boolean(a.type);
|
|
6267
6301
|
}
|
|
6268
6302
|
function capabilityHeadings(dir) {
|
|
6269
6303
|
const p2 = join(dir, KNOWLEDGE3, "product", "capabilities.md");
|
|
@@ -8007,7 +8041,7 @@ function runUnderstand() {
|
|
|
8007
8041
|
}
|
|
8008
8042
|
|
|
8009
8043
|
// src/core/classifier.ts
|
|
8010
|
-
import
|
|
8044
|
+
import path5 from "path";
|
|
8011
8045
|
var MIGRATION_PATTERNS = [
|
|
8012
8046
|
/supabase\/migrations\//,
|
|
8013
8047
|
/prisma\/migrations\//,
|
|
@@ -8046,7 +8080,7 @@ var DEP_PATTERNS = [
|
|
|
8046
8080
|
/^pom\.xml$/
|
|
8047
8081
|
];
|
|
8048
8082
|
function normalizePath2(p2) {
|
|
8049
|
-
return p2.split(
|
|
8083
|
+
return p2.split(path5.sep).join("/");
|
|
8050
8084
|
}
|
|
8051
8085
|
function matchesAny(file, patterns) {
|
|
8052
8086
|
const normalized = normalizePath2(file);
|
|
@@ -8878,10 +8912,10 @@ ${globs.map((g) => ` - ${g}`).join("\n")}`);
|
|
|
8878
8912
|
import { parse as parseYaml13, stringify as stringifyYaml5 } from "yaml";
|
|
8879
8913
|
var DESCRIPTOR_PATH2 = "knowledge/module.yml";
|
|
8880
8914
|
function readDescriptor(dir) {
|
|
8881
|
-
const
|
|
8882
|
-
if (!exists(
|
|
8915
|
+
const path6 = join(dir, DESCRIPTOR_PATH2);
|
|
8916
|
+
if (!exists(path6)) return null;
|
|
8883
8917
|
try {
|
|
8884
|
-
return parseYaml13(readFile(
|
|
8918
|
+
return parseYaml13(readFile(path6));
|
|
8885
8919
|
} catch {
|
|
8886
8920
|
return null;
|
|
8887
8921
|
}
|
|
@@ -10486,10 +10520,10 @@ function slugify3(name) {
|
|
|
10486
10520
|
return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
10487
10521
|
}
|
|
10488
10522
|
function readModulesDescriptor(dir) {
|
|
10489
|
-
const
|
|
10490
|
-
if (!exists(
|
|
10523
|
+
const path6 = join(dir, DESCRIPTOR_PATH3);
|
|
10524
|
+
if (!exists(path6)) return { version: 1, modules: [] };
|
|
10491
10525
|
try {
|
|
10492
|
-
const parsed = parseYaml14(readFile(
|
|
10526
|
+
const parsed = parseYaml14(readFile(path6));
|
|
10493
10527
|
return { version: parsed.version ?? 1, modules: parsed.modules ?? [] };
|
|
10494
10528
|
} catch {
|
|
10495
10529
|
return { version: 1, modules: [] };
|
|
@@ -10899,8 +10933,8 @@ var capsuleCmd = program.command("capsule").description("Export this project as
|
|
|
10899
10933
|
capsuleCmd.command("export").description("Write a Knowledge Capsule about this project to .kaddo/exports/").action(() => {
|
|
10900
10934
|
runCapsuleExport();
|
|
10901
10935
|
});
|
|
10902
|
-
capsuleCmd.command("add <path>").description("Register an external Knowledge Capsule as project context (.kaddo/external.yml)").action((
|
|
10903
|
-
runCapsuleAdd(
|
|
10936
|
+
capsuleCmd.command("add <path>").description("Register an external Knowledge Capsule as project context (.kaddo/external.yml)").action((path6) => {
|
|
10937
|
+
runCapsuleAdd(path6);
|
|
10904
10938
|
});
|
|
10905
10939
|
var graphCmd = program.command("graph").description("Export the lightweight, file-based knowledge graph of the project");
|
|
10906
10940
|
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) => {
|