@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.
Files changed (3) hide show
  1. package/README.md +1 -0
  2. package/dist/index.js +73 -39
  3. 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 path5 = i.path.join(".");
596
- return path5 ? ` - ${path5}: ${i.message}` : ` - ${i.message}`;
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 path5 = join(dir, IGNORE_FILE);
5111
- if (!exists(path5)) return [];
5132
+ const path6 = join(dir, IGNORE_FILE);
5133
+ if (!exists(path6)) return [];
5112
5134
  try {
5113
- const raw = readFile(path5);
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 path5 = join(dir, IGNORE_FILE);
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(path5, stringifyYaml2(existing));
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 path5 = join(dir, IGNORE_FILE);
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(path5, stringifyYaml2(filtered));
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 path5 = join(dir, DESCRIPTOR_PATH);
5162
- if (!exists(path5)) return [];
5183
+ const path6 = join(dir, DESCRIPTOR_PATH);
5184
+ if (!exists(path6)) return [];
5163
5185
  let parsed;
5164
5186
  try {
5165
- parsed = parseYaml5(readFile(path5));
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 path3 from "path";
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 = path3.relative(dir, artifact.filePath).split(path3.sep).join("/");
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 currentFiles = await getModifiedFiles(mode);
5584
- const untrackedFiles = opts.staged ? [] : await getUntrackedFiles();
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(touchedFiles);
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(touchedFiles);
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, path5, md) {
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: path5,
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 toPosix2(p2) {
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 toPosix2(a.filePath).includes("/tech/decisions/") && Boolean(a.type);
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 path5 = layer2.files.map((f) => `${KNOWLEDGE2}/${f}`).find((rel) => exists(join(dir, rel)));
6099
- if (path5) {
6100
- addNode({ id: layer2.id, type: layer2.type, label: layer2.label, path: path5 });
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 toPosix3(p2) {
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 toPosix3(a.filePath).includes("/tech/decisions/") && Boolean(a.type);
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 path4 from "path";
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(path4.sep).join("/");
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 path5 = join(dir, DESCRIPTOR_PATH2);
8882
- if (!exists(path5)) return null;
8915
+ const path6 = join(dir, DESCRIPTOR_PATH2);
8916
+ if (!exists(path6)) return null;
8883
8917
  try {
8884
- return parseYaml13(readFile(path5));
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 path5 = join(dir, DESCRIPTOR_PATH3);
10490
- if (!exists(path5)) return { version: 1, modules: [] };
10523
+ const path6 = join(dir, DESCRIPTOR_PATH3);
10524
+ if (!exists(path6)) return { version: 1, modules: [] };
10491
10525
  try {
10492
- const parsed = parseYaml14(readFile(path5));
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((path5) => {
10903
- runCapsuleAdd(path5);
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) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaddo/cli",
3
- "version": "3.22.0",
3
+ "version": "3.22.1",
4
4
  "description": "Knowledge Driven Development toolkit",
5
5
  "license": "MIT",
6
6
  "repository": {