@kaddo/cli 3.21.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 +2 -0
- package/dist/index.js +144 -45
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -508,6 +508,8 @@ 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`) |
|
|
512
|
+
| v3.22.1 | Guard project-root path normalization: matches `code:` globs when the project is a subfolder of the Git root |
|
|
511
513
|
|
|
512
514
|
**Optional modules (installed with `kaddo add`):**
|
|
513
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,10 +5546,17 @@ 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) {
|
|
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,
|
|
5553
|
+
ownership_scope: {
|
|
5554
|
+
included: includeArchived ? ["draft", "ready", "in-progress", "blocked", "completed", "archived"] : ["draft", "ready", "in-progress", "blocked", "completed"],
|
|
5555
|
+
excluded: includeArchived ? [] : ["archived"]
|
|
5556
|
+
},
|
|
5557
|
+
// Git-root-relative → project-root-relative mapping used for matching (VS-060.1).
|
|
5558
|
+
normalized_files: normalizedFiles,
|
|
5559
|
+
files_outside_project: outsideProjectFiles,
|
|
5531
5560
|
touched_files: touchedFiles.length,
|
|
5532
5561
|
fyi_count: activeMatches.length,
|
|
5533
5562
|
ignored_count: ignoredCount,
|
|
@@ -5556,6 +5585,12 @@ function printCIJson(dir, touchedFiles, activeMatches, ignoredCount, pluginSigna
|
|
|
5556
5585
|
}
|
|
5557
5586
|
console.log(JSON.stringify(output, null, 2));
|
|
5558
5587
|
}
|
|
5588
|
+
function printOwnershipScope(includeArchived) {
|
|
5589
|
+
console.log("Ownership scope:");
|
|
5590
|
+
console.log("- Active and completed Work Items");
|
|
5591
|
+
console.log(includeArchived ? "- Archived Work Items included (--include-archived)" : "- Archived Work Items excluded");
|
|
5592
|
+
console.log("");
|
|
5593
|
+
}
|
|
5559
5594
|
async function runGuard(opts = {}) {
|
|
5560
5595
|
const dir = cwd();
|
|
5561
5596
|
const interactive = opts.interactive !== false && !opts.ci && !opts.json;
|
|
@@ -5570,8 +5605,16 @@ async function runGuard(opts = {}) {
|
|
|
5570
5605
|
const ignores = loadIgnores(dir);
|
|
5571
5606
|
const plugins = resolvePlugins(config.plugins ?? []);
|
|
5572
5607
|
const mode = opts.staged ? "staged" : "head";
|
|
5573
|
-
const
|
|
5574
|
-
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);
|
|
5575
5618
|
const printUntracked = () => {
|
|
5576
5619
|
if (untrackedFiles.length === 0 || jsonMode) return;
|
|
5577
5620
|
console.log("");
|
|
@@ -5586,6 +5629,7 @@ async function runGuard(opts = {}) {
|
|
|
5586
5629
|
}
|
|
5587
5630
|
const workspaceFiles = workspaceScan ? workspaceScan.changedFiles.map((c) => c.normalizedPath) : [];
|
|
5588
5631
|
const touchedFiles = [.../* @__PURE__ */ new Set([...currentFiles, ...workspaceFiles])];
|
|
5632
|
+
const displayTouched = [.../* @__PURE__ */ new Set([...inProjectPairs.map((p2) => p2.raw), ...workspaceFiles])];
|
|
5589
5633
|
if (touchedFiles.length === 0) {
|
|
5590
5634
|
if (workspaceScan) printWorkspaceHeader(workspaceScan);
|
|
5591
5635
|
console.log("kaddo guard: no modified files detected.");
|
|
@@ -5597,7 +5641,10 @@ async function runGuard(opts = {}) {
|
|
|
5597
5641
|
console.log("kaddo guard: no knowledge/ directory found. Run `kaddo init` first.");
|
|
5598
5642
|
return;
|
|
5599
5643
|
}
|
|
5600
|
-
const
|
|
5644
|
+
const includeArchived = opts.includeArchived === true;
|
|
5645
|
+
const artifacts = discoverKnowledge(dir).filter(
|
|
5646
|
+
(a) => !(a.isWorkItem && a.lifecycle === "archived" && !includeArchived)
|
|
5647
|
+
);
|
|
5601
5648
|
const result = analyzeGuard(touchedFiles, artifacts, silentWithoutOwnership);
|
|
5602
5649
|
const pluginSignals = runPlugins(plugins, currentFiles, (filePath) => {
|
|
5603
5650
|
const abs = join(dir, filePath);
|
|
@@ -5618,7 +5665,7 @@ async function runGuard(opts = {}) {
|
|
|
5618
5665
|
const ownerMapCI = loadOwners(dir);
|
|
5619
5666
|
const matchedDomainsCI = collectMatchedDomains(activeMatches.map((m) => m.artifact.domains));
|
|
5620
5667
|
const affectedOwnersCI = resolveAffectedOwners(matchedDomainsCI, ownerMapCI);
|
|
5621
|
-
printCIJson(dir, touchedFiles, activeMatches, alreadyIgnoredMatches.length, pluginSignals, affectedOwnersCI, workspaceScan);
|
|
5668
|
+
printCIJson(dir, touchedFiles, activeMatches, alreadyIgnoredMatches.length, pluginSignals, affectedOwnersCI, workspaceScan, includeArchived, inProjectPairs.map((p2) => ({ raw_path: p2.raw, project_path: p2.project })), outsideProjectFiles);
|
|
5622
5669
|
return;
|
|
5623
5670
|
}
|
|
5624
5671
|
if (result.silenced && pluginSignals.length === 0) {
|
|
@@ -5627,13 +5674,19 @@ async function runGuard(opts = {}) {
|
|
|
5627
5674
|
}
|
|
5628
5675
|
if (result.matches.length === 0 && pluginSignals.length === 0) {
|
|
5629
5676
|
if (workspaceScan) printWorkspaceHeader(workspaceScan);
|
|
5630
|
-
printHeader(
|
|
5677
|
+
printHeader(displayTouched);
|
|
5678
|
+
printOwnershipScope(includeArchived);
|
|
5631
5679
|
console.log(" No artifact ownership matches found.");
|
|
5680
|
+
console.log("");
|
|
5681
|
+
console.log("Note:");
|
|
5682
|
+
console.log("Guard checks ownership from active and completed Work Items.");
|
|
5683
|
+
console.log("Run `kaddo explain` to inspect ownership coverage.");
|
|
5632
5684
|
printUntracked();
|
|
5633
5685
|
return;
|
|
5634
5686
|
}
|
|
5635
5687
|
if (workspaceScan) printWorkspaceHeader(workspaceScan);
|
|
5636
|
-
printHeader(
|
|
5688
|
+
printHeader(displayTouched);
|
|
5689
|
+
printOwnershipScope(includeArchived);
|
|
5637
5690
|
printUntracked();
|
|
5638
5691
|
for (const match of activeMatches) {
|
|
5639
5692
|
printFYI(match, dir);
|
|
@@ -5980,7 +6033,7 @@ function sectionParagraph(md, title) {
|
|
|
5980
6033
|
}
|
|
5981
6034
|
return "";
|
|
5982
6035
|
}
|
|
5983
|
-
function parseCapsule(id,
|
|
6036
|
+
function parseCapsule(id, path6, md) {
|
|
5984
6037
|
const { data } = matter2(md);
|
|
5985
6038
|
const updatedAt = data.updated_at ? String(data.updated_at) : void 0;
|
|
5986
6039
|
let ageDays = null;
|
|
@@ -5990,7 +6043,7 @@ function parseCapsule(id, path5, md) {
|
|
|
5990
6043
|
}
|
|
5991
6044
|
return {
|
|
5992
6045
|
id,
|
|
5993
|
-
path:
|
|
6046
|
+
path: path6,
|
|
5994
6047
|
system: data.system ? String(data.system) : id,
|
|
5995
6048
|
owner: data.owner ? String(data.owner) : void 0,
|
|
5996
6049
|
updatedAt,
|
|
@@ -6040,14 +6093,19 @@ function loadExternalCapsules(dir) {
|
|
|
6040
6093
|
|
|
6041
6094
|
// src/core/graph.ts
|
|
6042
6095
|
var KNOWLEDGE2 = "knowledge";
|
|
6043
|
-
|
|
6096
|
+
var ACTIVE_STATUSES = ["draft", "ready", "in-progress", "blocked"];
|
|
6097
|
+
var ALL_STATUSES = ["draft", "ready", "in-progress", "blocked", "completed"];
|
|
6098
|
+
function scopeStatuses(scope) {
|
|
6099
|
+
return scope === "all" ? { included: ALL_STATUSES, excluded: ["archived"] } : { included: ACTIVE_STATUSES, excluded: ["completed", "archived"] };
|
|
6100
|
+
}
|
|
6101
|
+
function toPosix3(p2) {
|
|
6044
6102
|
return p2.replace(/\\/g, "/");
|
|
6045
6103
|
}
|
|
6046
6104
|
function slug(s) {
|
|
6047
6105
|
return s.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
6048
6106
|
}
|
|
6049
6107
|
function isAdr(a) {
|
|
6050
|
-
return
|
|
6108
|
+
return toPosix3(a.filePath).includes("/tech/decisions/") && Boolean(a.type);
|
|
6051
6109
|
}
|
|
6052
6110
|
function buildGraph(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
|
|
6053
6111
|
const scope = opts.scope ?? "active";
|
|
@@ -6071,9 +6129,9 @@ function buildGraph(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
|
|
|
6071
6129
|
];
|
|
6072
6130
|
const presentLayers = [];
|
|
6073
6131
|
for (const layer2 of layerDocs) {
|
|
6074
|
-
const
|
|
6075
|
-
if (
|
|
6076
|
-
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 });
|
|
6077
6135
|
presentLayers.push(layer2.id);
|
|
6078
6136
|
}
|
|
6079
6137
|
}
|
|
@@ -6082,7 +6140,10 @@ function buildGraph(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
|
|
|
6082
6140
|
}
|
|
6083
6141
|
const all = discoverKnowledge(dir);
|
|
6084
6142
|
const workItems = all.filter((a) => a.isWorkItem);
|
|
6085
|
-
const
|
|
6143
|
+
const { included, excluded } = scopeStatuses(scope);
|
|
6144
|
+
const includedSet = new Set(included);
|
|
6145
|
+
const activeWICount = workItems.filter((a) => a.lifecycle && isActiveState(a.lifecycle)).length;
|
|
6146
|
+
const selectedWIs = workItems.filter((a) => a.lifecycle && includedSet.has(a.lifecycle));
|
|
6086
6147
|
for (const wi of selectedWIs) {
|
|
6087
6148
|
const id = wi.id || wi.title;
|
|
6088
6149
|
const wiNodeId = `wi:${id}`;
|
|
@@ -6152,6 +6213,7 @@ function buildGraph(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
|
|
|
6152
6213
|
}
|
|
6153
6214
|
}
|
|
6154
6215
|
}
|
|
6216
|
+
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
6217
|
return {
|
|
6156
6218
|
generated_at: now.toISOString(),
|
|
6157
6219
|
project: {
|
|
@@ -6159,6 +6221,10 @@ function buildGraph(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
|
|
|
6159
6221
|
state: config.project.state,
|
|
6160
6222
|
structure: config.project.structure
|
|
6161
6223
|
},
|
|
6224
|
+
scope,
|
|
6225
|
+
scope_reason: scopeReason,
|
|
6226
|
+
included_statuses: included,
|
|
6227
|
+
excluded_statuses: excluded,
|
|
6162
6228
|
nodes: [...nodes.values()],
|
|
6163
6229
|
edges
|
|
6164
6230
|
};
|
|
@@ -6206,8 +6272,13 @@ function loadGraphSummary(dir) {
|
|
|
6206
6272
|
const connected = new Set(
|
|
6207
6273
|
edges.filter((e) => e.type === "owns" && activeWiIds.has(e.from)).map((e) => e.from)
|
|
6208
6274
|
);
|
|
6275
|
+
const scope = graph.scope === "all" ? "all" : "active";
|
|
6209
6276
|
return {
|
|
6210
6277
|
generatedAt: String(graph.generated_at ?? ""),
|
|
6278
|
+
scope,
|
|
6279
|
+
scopeReason: String(graph.scope_reason ?? ""),
|
|
6280
|
+
includedStatuses: Array.isArray(graph.included_statuses) ? graph.included_statuses : [],
|
|
6281
|
+
excludedStatuses: Array.isArray(graph.excluded_statuses) ? graph.excluded_statuses : [],
|
|
6211
6282
|
nodes: nodes.length,
|
|
6212
6283
|
edges: edges.length,
|
|
6213
6284
|
activeWorkItemsConnectedToCode: connected.size
|
|
@@ -6219,14 +6290,14 @@ function loadGraphSummary(dir) {
|
|
|
6219
6290
|
|
|
6220
6291
|
// src/core/graph-hints.ts
|
|
6221
6292
|
var KNOWLEDGE3 = "knowledge";
|
|
6222
|
-
function
|
|
6293
|
+
function toPosix4(p2) {
|
|
6223
6294
|
return p2.replace(/\\/g, "/");
|
|
6224
6295
|
}
|
|
6225
6296
|
function slug2(s) {
|
|
6226
6297
|
return s.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
6227
6298
|
}
|
|
6228
6299
|
function isAdr2(a) {
|
|
6229
|
-
return
|
|
6300
|
+
return toPosix4(a.filePath).includes("/tech/decisions/") && Boolean(a.type);
|
|
6230
6301
|
}
|
|
6231
6302
|
function capabilityHeadings(dir) {
|
|
6232
6303
|
const p2 = join(dir, KNOWLEDGE3, "product", "capabilities.md");
|
|
@@ -6359,6 +6430,8 @@ function buildGraphHints(dir, graph, now = /* @__PURE__ */ new Date()) {
|
|
|
6359
6430
|
const quality = assessQuality(nodes, relationshipEdges, hints.length);
|
|
6360
6431
|
return {
|
|
6361
6432
|
generated_at: now.toISOString(),
|
|
6433
|
+
scope: graph.scope,
|
|
6434
|
+
scope_reason: graph.scope_reason,
|
|
6362
6435
|
quality,
|
|
6363
6436
|
summary: { nodes, edges: graph.edges.length, hints: hints.length },
|
|
6364
6437
|
metrics,
|
|
@@ -6394,13 +6467,23 @@ function renderGraphHintsMarkdown(report) {
|
|
|
6394
6467
|
lines.push("");
|
|
6395
6468
|
lines.push("## Summary");
|
|
6396
6469
|
lines.push("");
|
|
6470
|
+
lines.push(`- Scope: ${report.scope} \u2014 ${report.scope_reason}`);
|
|
6397
6471
|
lines.push(`- Relationship quality: ${report.quality} \u2014 ${QUALITY_NOTE[report.quality]}`);
|
|
6398
6472
|
lines.push(`- Nodes: ${report.summary.nodes}`);
|
|
6399
6473
|
lines.push(`- Edges: ${report.summary.edges}`);
|
|
6400
6474
|
lines.push(`- Hints: ${report.summary.hints}`);
|
|
6401
6475
|
lines.push("");
|
|
6402
6476
|
if (report.hints.length === 0) {
|
|
6403
|
-
|
|
6477
|
+
if (report.quality === "empty") {
|
|
6478
|
+
lines.push("No active relationship hints were generated.");
|
|
6479
|
+
lines.push("");
|
|
6480
|
+
lines.push(`The graph is ${report.quality} for the **${report.scope}** scope \u2014 ${report.scope_reason}`);
|
|
6481
|
+
if (report.scope !== "all") {
|
|
6482
|
+
lines.push("Run `kaddo graph export --scope all` to inspect completed Work Items and historical relationships.");
|
|
6483
|
+
}
|
|
6484
|
+
} else {
|
|
6485
|
+
lines.push("No hints \u2014 the declared relationships look healthy. \u{1F389}");
|
|
6486
|
+
}
|
|
6404
6487
|
lines.push("");
|
|
6405
6488
|
return lines.join("\n");
|
|
6406
6489
|
}
|
|
@@ -6436,6 +6519,8 @@ function loadGraphHints(dir) {
|
|
|
6436
6519
|
const report = JSON.parse(readFile(p2));
|
|
6437
6520
|
const hints = Array.isArray(report.hints) ? report.hints : [];
|
|
6438
6521
|
return {
|
|
6522
|
+
scope: String(report.scope ?? "active"),
|
|
6523
|
+
scopeReason: String(report.scope_reason ?? ""),
|
|
6439
6524
|
quality: report.quality ?? "empty",
|
|
6440
6525
|
totalHints: hints.length,
|
|
6441
6526
|
activeWorkItemHints: hints.filter((h) => h.artifact_type === "work-item").length,
|
|
@@ -6959,13 +7044,18 @@ function renderExplanationHuman(exp) {
|
|
|
6959
7044
|
}
|
|
6960
7045
|
if (exp.graph) {
|
|
6961
7046
|
lines.push("## Knowledge Graph");
|
|
7047
|
+
lines.push(`- Scope: ${exp.graph.scope}`);
|
|
6962
7048
|
lines.push(`- Nodes: ${exp.graph.nodes}`);
|
|
6963
7049
|
lines.push(`- Edges: ${exp.graph.edges}`);
|
|
6964
7050
|
if (exp.graphHints) {
|
|
6965
7051
|
lines.push(`- Quality: ${exp.graphHints.quality}`);
|
|
6966
7052
|
lines.push(`- Hints: ${exp.graphHints.totalHints}`);
|
|
6967
7053
|
}
|
|
7054
|
+
if (exp.graph.scopeReason) lines.push(`- Reason: ${exp.graph.scopeReason}`);
|
|
6968
7055
|
if (exp.graph.generatedAt) lines.push(`- Last exported: ${exp.graph.generatedAt}`);
|
|
7056
|
+
if (exp.graph.scope === "active" && exp.graphHints?.quality === "empty") {
|
|
7057
|
+
lines.push("- Tip: Run `kaddo graph export --scope all` to include completed Work Items");
|
|
7058
|
+
}
|
|
6969
7059
|
lines.push("");
|
|
6970
7060
|
}
|
|
6971
7061
|
if (exp.skills.total > 0) {
|
|
@@ -7557,9 +7647,12 @@ function renderContextPack(pack) {
|
|
|
7557
7647
|
parts.push(
|
|
7558
7648
|
[
|
|
7559
7649
|
"- Available: yes",
|
|
7650
|
+
`- Scope: ${pack.graph.scope}`,
|
|
7560
7651
|
`- Nodes: ${pack.graph.nodes}`,
|
|
7561
7652
|
`- Edges: ${pack.graph.edges}`,
|
|
7562
|
-
|
|
7653
|
+
...pack.graphHints ? [`- Quality: ${pack.graphHints.quality}`] : [],
|
|
7654
|
+
`- Active Work Items connected to code: ${pack.graph.activeWorkItemsConnectedToCode}`,
|
|
7655
|
+
...pack.graph.scopeReason ? [`- Reason: ${pack.graph.scopeReason}`] : []
|
|
7563
7656
|
].join("\n") + "\n"
|
|
7564
7657
|
);
|
|
7565
7658
|
parts.push("Full graph: `.kaddo/graph.json` / `.kaddo/graph.mmd` (run `kaddo graph export` to refresh).\n");
|
|
@@ -7948,7 +8041,7 @@ function runUnderstand() {
|
|
|
7948
8041
|
}
|
|
7949
8042
|
|
|
7950
8043
|
// src/core/classifier.ts
|
|
7951
|
-
import
|
|
8044
|
+
import path5 from "path";
|
|
7952
8045
|
var MIGRATION_PATTERNS = [
|
|
7953
8046
|
/supabase\/migrations\//,
|
|
7954
8047
|
/prisma\/migrations\//,
|
|
@@ -7987,7 +8080,7 @@ var DEP_PATTERNS = [
|
|
|
7987
8080
|
/^pom\.xml$/
|
|
7988
8081
|
];
|
|
7989
8082
|
function normalizePath2(p2) {
|
|
7990
|
-
return p2.split(
|
|
8083
|
+
return p2.split(path5.sep).join("/");
|
|
7991
8084
|
}
|
|
7992
8085
|
function matchesAny(file, patterns) {
|
|
7993
8086
|
const normalized = normalizePath2(file);
|
|
@@ -8819,10 +8912,10 @@ ${globs.map((g) => ` - ${g}`).join("\n")}`);
|
|
|
8819
8912
|
import { parse as parseYaml13, stringify as stringifyYaml5 } from "yaml";
|
|
8820
8913
|
var DESCRIPTOR_PATH2 = "knowledge/module.yml";
|
|
8821
8914
|
function readDescriptor(dir) {
|
|
8822
|
-
const
|
|
8823
|
-
if (!exists(
|
|
8915
|
+
const path6 = join(dir, DESCRIPTOR_PATH2);
|
|
8916
|
+
if (!exists(path6)) return null;
|
|
8824
8917
|
try {
|
|
8825
|
-
return parseYaml13(readFile(
|
|
8918
|
+
return parseYaml13(readFile(path6));
|
|
8826
8919
|
} catch {
|
|
8827
8920
|
return null;
|
|
8828
8921
|
}
|
|
@@ -10427,10 +10520,10 @@ function slugify3(name) {
|
|
|
10427
10520
|
return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
10428
10521
|
}
|
|
10429
10522
|
function readModulesDescriptor(dir) {
|
|
10430
|
-
const
|
|
10431
|
-
if (!exists(
|
|
10523
|
+
const path6 = join(dir, DESCRIPTOR_PATH3);
|
|
10524
|
+
if (!exists(path6)) return { version: 1, modules: [] };
|
|
10432
10525
|
try {
|
|
10433
|
-
const parsed = parseYaml14(readFile(
|
|
10526
|
+
const parsed = parseYaml14(readFile(path6));
|
|
10434
10527
|
return { version: parsed.version ?? 1, modules: parsed.modules ?? [] };
|
|
10435
10528
|
} catch {
|
|
10436
10529
|
return { version: 1, modules: [] };
|
|
@@ -10797,9 +10890,15 @@ function runGraphExport(opts = {}) {
|
|
|
10797
10890
|
writeFile(join(dir, ".kaddo", "graph-hints.md"), renderGraphHintsMarkdown(hints));
|
|
10798
10891
|
writeFile(join(dir, ".kaddo", "graph-hints.json"), serializeGraphHintsJson(hints));
|
|
10799
10892
|
written.push(".kaddo/graph-hints.md", ".kaddo/graph-hints.json");
|
|
10800
|
-
log2.success(
|
|
10893
|
+
log2.success(`Knowledge graph exported with ${scope} scope.`);
|
|
10801
10894
|
log2.info(`Scope: ${scope} \xB7 Nodes: ${graph.nodes.length} \xB7 Edges: ${graph.edges.length}`);
|
|
10802
10895
|
log2.info(`Relationship quality: ${hints.quality}`);
|
|
10896
|
+
const hasWorkItemNodes = graph.nodes.some((n) => n.type === "work-item");
|
|
10897
|
+
if (scope === "active" && !hasWorkItemNodes) {
|
|
10898
|
+
log2.warn("No active Work Items found.");
|
|
10899
|
+
log2.info("The active graph only includes knowledge layers.");
|
|
10900
|
+
log2.info("Run `kaddo graph export --scope all` to include completed Work Items.");
|
|
10901
|
+
}
|
|
10803
10902
|
if (hints.hints.length > 0) {
|
|
10804
10903
|
const shown = hints.hints.slice(0, 5);
|
|
10805
10904
|
log2.warn(`${hints.hints.length} metadata hint(s) available:`);
|
|
@@ -10834,14 +10933,14 @@ var capsuleCmd = program.command("capsule").description("Export this project as
|
|
|
10834
10933
|
capsuleCmd.command("export").description("Write a Knowledge Capsule about this project to .kaddo/exports/").action(() => {
|
|
10835
10934
|
runCapsuleExport();
|
|
10836
10935
|
});
|
|
10837
|
-
capsuleCmd.command("add <path>").description("Register an external Knowledge Capsule as project context (.kaddo/external.yml)").action((
|
|
10838
|
-
runCapsuleAdd(
|
|
10936
|
+
capsuleCmd.command("add <path>").description("Register an external Knowledge Capsule as project context (.kaddo/external.yml)").action((path6) => {
|
|
10937
|
+
runCapsuleAdd(path6);
|
|
10839
10938
|
});
|
|
10840
10939
|
var graphCmd = program.command("graph").description("Export the lightweight, file-based knowledge graph of the project");
|
|
10841
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) => {
|
|
10842
10941
|
runGraphExport(opts);
|
|
10843
10942
|
});
|
|
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) => {
|
|
10943
|
+
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
10944
|
await runGuard(opts);
|
|
10846
10945
|
});
|
|
10847
10946
|
var ignoreCmd = program.command("ignore").description("Manage guard ignore list");
|