@kaddo/cli 3.8.0 → 3.9.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/dist/index.js +222 -143
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -725,6 +725,17 @@ async function runScan() {
|
|
|
725
725
|
}
|
|
726
726
|
|
|
727
727
|
// src/core/knowledge-levels.ts
|
|
728
|
+
var WORK_ITEM_TYPES = ["feature", "bugfix", "hotfix", "spike", "chore"];
|
|
729
|
+
var TYPE_ALIASES = {
|
|
730
|
+
setup: "chore",
|
|
731
|
+
maintenance: "chore",
|
|
732
|
+
tooling: "chore",
|
|
733
|
+
infrastructure: "chore",
|
|
734
|
+
infra: "chore",
|
|
735
|
+
config: "chore",
|
|
736
|
+
configuration: "chore",
|
|
737
|
+
refactor: "chore"
|
|
738
|
+
};
|
|
728
739
|
var LEVELS = {
|
|
729
740
|
K0: {
|
|
730
741
|
level: "K0",
|
|
@@ -879,7 +890,9 @@ var TYPE_TO_LEVEL = {
|
|
|
879
890
|
hotfix: "K1",
|
|
880
891
|
bugfix: "K2",
|
|
881
892
|
feature: "K2",
|
|
882
|
-
spike: "K3"
|
|
893
|
+
spike: "K3",
|
|
894
|
+
// Chores are maintenance/config/tooling work — low ceremony (problem + expected result).
|
|
895
|
+
chore: "K1"
|
|
883
896
|
};
|
|
884
897
|
function getLevel(level) {
|
|
885
898
|
return LEVELS[level];
|
|
@@ -888,7 +901,13 @@ function getLevelForType(type) {
|
|
|
888
901
|
return TYPE_TO_LEVEL[type];
|
|
889
902
|
}
|
|
890
903
|
function isValidType(type) {
|
|
891
|
-
return
|
|
904
|
+
return WORK_ITEM_TYPES.includes(type);
|
|
905
|
+
}
|
|
906
|
+
function normalizeType(raw) {
|
|
907
|
+
if (!raw) return null;
|
|
908
|
+
const t = raw.trim().toLowerCase();
|
|
909
|
+
if (isValidType(t)) return t;
|
|
910
|
+
return TYPE_ALIASES[t] ?? null;
|
|
892
911
|
}
|
|
893
912
|
|
|
894
913
|
// src/modules/adr.ts
|
|
@@ -1832,6 +1851,9 @@ Produce a roadmap where each initiative includes:
|
|
|
1832
1851
|
7. Dependencies.
|
|
1833
1852
|
8. Why this comes now.
|
|
1834
1853
|
9. Candidate work items (each with type, suggested knowledge level, expected value, notes).
|
|
1854
|
+
Use only the official Work Item types: \`feature\`, \`bugfix\`, \`hotfix\`, \`spike\`, \`chore\`.
|
|
1855
|
+
Use \`chore\` for technical/maintenance/tooling/config/infra work (e.g. "Initialize
|
|
1856
|
+
TypeScript project", "Configure Vitest", "Setup CI") \u2014 do not label such work \`feature\`.
|
|
1835
1857
|
10. Open questions.
|
|
1836
1858
|
|
|
1837
1859
|
Then add a suggested execution order, risks and constraints, a "Not Now" list, and the
|
|
@@ -2133,12 +2155,14 @@ A refined Work Item intended to be saved under the lifecycle workspace:
|
|
|
2133
2155
|
|
|
2134
2156
|
1. Restate the problem in one clear sentence.
|
|
2135
2157
|
2. Split the candidate if it is too large for a single Work Item.
|
|
2136
|
-
3.
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2158
|
+
3. Preserve the candidate's type (\`feature\`, \`bugfix\`, \`hotfix\`, \`spike\`, \`chore\`).
|
|
2159
|
+
Keep \`chore\` for maintenance/tooling/config/infra work \u2014 never upgrade a chore to a feature.
|
|
2160
|
+
4. Validate the Knowledge Level (K0\u2013K4) and propose a different one if needed.
|
|
2161
|
+
5. Propose acceptance criteria.
|
|
2162
|
+
6. Propose Out of scope and Validation sections.
|
|
2163
|
+
7. Propose a Definition of Done.
|
|
2164
|
+
8. Identify open questions and assumptions.
|
|
2165
|
+
9. Suggest ownership candidates (code globs) if evident.
|
|
2142
2166
|
|
|
2143
2167
|
## Constraints
|
|
2144
2168
|
|
|
@@ -3675,20 +3699,21 @@ async function runCreate(type, opts = {}) {
|
|
|
3675
3699
|
}
|
|
3676
3700
|
if (!type) {
|
|
3677
3701
|
console.error("Missing work item type.");
|
|
3678
|
-
console.error("Usage: kaddo create <feature|bugfix|hotfix|spike> [--from roadmap]");
|
|
3702
|
+
console.error("Usage: kaddo create <feature|bugfix|hotfix|spike|chore> [--from roadmap]");
|
|
3679
3703
|
process.exit(1);
|
|
3680
3704
|
}
|
|
3681
3705
|
const modWorkItemType = findWorkItemType(type);
|
|
3682
3706
|
if (modWorkItemType) {
|
|
3683
3707
|
return runCreateModule(dir, modWorkItemType);
|
|
3684
3708
|
}
|
|
3685
|
-
|
|
3709
|
+
const workItemType = normalizeType(type);
|
|
3710
|
+
if (!workItemType) {
|
|
3686
3711
|
console.error(`Unknown work item type: "${type}"`);
|
|
3687
|
-
console.error("Valid types: feature, bugfix, hotfix, spike");
|
|
3712
|
+
console.error("Valid types: feature, bugfix, hotfix, spike, chore");
|
|
3713
|
+
console.error("Aliases: setup, maintenance, tooling, infrastructure, refactor \u2192 chore");
|
|
3688
3714
|
console.error("Module types: adr, incident, rfc, migration, legacy (run `kaddo add <module>` first)");
|
|
3689
3715
|
process.exit(1);
|
|
3690
3716
|
}
|
|
3691
|
-
const workItemType = type;
|
|
3692
3717
|
const level = getLevelForType(workItemType);
|
|
3693
3718
|
const levelDef = getLevel(level);
|
|
3694
3719
|
intro2(`kaddo create ${workItemType}`);
|
|
@@ -3696,7 +3721,7 @@ async function runCreate(type, opts = {}) {
|
|
|
3696
3721
|
printStateGuidance(dir);
|
|
3697
3722
|
const title = await text2({
|
|
3698
3723
|
message: "Title for this work item",
|
|
3699
|
-
placeholder: `e.g. ${workItemType === "feature" ? "Add email verification to checkout" : workItemType === "hotfix" ? "Fix null pointer in payment handler" : workItemType === "bugfix" ? "Fix broken pagination on orders list" : "Explore caching strategies for API responses"}`,
|
|
3724
|
+
placeholder: `e.g. ${workItemType === "feature" ? "Add email verification to checkout" : workItemType === "hotfix" ? "Fix null pointer in payment handler" : workItemType === "bugfix" ? "Fix broken pagination on orders list" : workItemType === "chore" ? "Configure Vitest and CI pipeline" : "Explore caching strategies for API responses"}`,
|
|
3700
3725
|
validate: (v) => v.trim().length === 0 ? "Title is required." : void 0
|
|
3701
3726
|
});
|
|
3702
3727
|
const answers = {};
|
|
@@ -3765,9 +3790,7 @@ ${body}`;
|
|
|
3765
3790
|
}
|
|
3766
3791
|
var K_LEVEL_RE = /^K[0-4]$/;
|
|
3767
3792
|
function resolveCandidateType(candidate, cliType) {
|
|
3768
|
-
|
|
3769
|
-
if (cliType && isValidType(cliType)) return cliType;
|
|
3770
|
-
return null;
|
|
3793
|
+
return normalizeType(candidate.type) ?? normalizeType(cliType);
|
|
3771
3794
|
}
|
|
3772
3795
|
function resolveCandidateLevel(candidate, type) {
|
|
3773
3796
|
const suggested = candidate.suggestedKnowledgeLevel?.trim().toUpperCase();
|
|
@@ -3929,7 +3952,8 @@ async function runCreateFromRoadmap(dir, cliType) {
|
|
|
3929
3952
|
{ value: "feature", label: "feature" },
|
|
3930
3953
|
{ value: "bugfix", label: "bugfix" },
|
|
3931
3954
|
{ value: "hotfix", label: "hotfix" },
|
|
3932
|
-
{ value: "spike", label: "spike" }
|
|
3955
|
+
{ value: "spike", label: "spike" },
|
|
3956
|
+
{ value: "chore", label: "chore" }
|
|
3933
3957
|
]
|
|
3934
3958
|
});
|
|
3935
3959
|
}
|
|
@@ -4020,9 +4044,11 @@ function parseArtifact(filePath, raw) {
|
|
|
4020
4044
|
knowledgeLevel: String(data.knowledge_level ?? ""),
|
|
4021
4045
|
codeGlobs: globs,
|
|
4022
4046
|
domains: Array.isArray(data.domains) ? data.domains : [],
|
|
4047
|
+
capabilities: Array.isArray(data.capabilities) ? data.capabilities.map(String).filter(Boolean) : [],
|
|
4023
4048
|
status: String(data.status ?? ""),
|
|
4024
4049
|
phase: String(data.phase ?? ""),
|
|
4025
|
-
initiative: String(data.initiative ?? data.source_initiative ?? "")
|
|
4050
|
+
initiative: String(data.initiative ?? data.source_initiative ?? ""),
|
|
4051
|
+
source: data.source ? String(data.source) : ""
|
|
4026
4052
|
};
|
|
4027
4053
|
} catch {
|
|
4028
4054
|
return null;
|
|
@@ -4047,6 +4073,94 @@ function readArtifacts(archDir) {
|
|
|
4047
4073
|
return artifacts;
|
|
4048
4074
|
}
|
|
4049
4075
|
|
|
4076
|
+
// src/core/lifecycle.ts
|
|
4077
|
+
var LIFECYCLE_STATES = [
|
|
4078
|
+
"draft",
|
|
4079
|
+
"ready",
|
|
4080
|
+
"in-progress",
|
|
4081
|
+
"blocked",
|
|
4082
|
+
"completed",
|
|
4083
|
+
"archived"
|
|
4084
|
+
];
|
|
4085
|
+
var ACTIVE_STATES = ["draft", "ready", "in-progress", "blocked"];
|
|
4086
|
+
var LEGACY_STATUS_MAP = {
|
|
4087
|
+
done: "completed",
|
|
4088
|
+
cancelled: "archived",
|
|
4089
|
+
canceled: "archived",
|
|
4090
|
+
"in-progress": "in-progress"
|
|
4091
|
+
};
|
|
4092
|
+
var STATE_SET = new Set(LIFECYCLE_STATES);
|
|
4093
|
+
function isLifecycleState(s) {
|
|
4094
|
+
return STATE_SET.has(s);
|
|
4095
|
+
}
|
|
4096
|
+
function isActiveState(s) {
|
|
4097
|
+
return ACTIVE_STATES.includes(s);
|
|
4098
|
+
}
|
|
4099
|
+
function lifecycleFolderOf(filePath) {
|
|
4100
|
+
const p2 = filePath.replace(/\\/g, "/");
|
|
4101
|
+
const m = p2.match(/\/delivery\/work-items\/([^/]+)\//);
|
|
4102
|
+
if (m && isLifecycleState(m[1])) return m[1];
|
|
4103
|
+
return null;
|
|
4104
|
+
}
|
|
4105
|
+
function lifecycleStateOf(item) {
|
|
4106
|
+
const status = (item.status ?? "").trim().toLowerCase();
|
|
4107
|
+
if (isLifecycleState(status)) return status;
|
|
4108
|
+
if (status && LEGACY_STATUS_MAP[status]) return LEGACY_STATUS_MAP[status];
|
|
4109
|
+
const folder = lifecycleFolderOf(item.filePath ?? "");
|
|
4110
|
+
if (folder) return folder;
|
|
4111
|
+
return "ready";
|
|
4112
|
+
}
|
|
4113
|
+
function emptyLifecycleCounts() {
|
|
4114
|
+
return Object.fromEntries(LIFECYCLE_STATES.map((s) => [s, 0]));
|
|
4115
|
+
}
|
|
4116
|
+
function lifecycleCounts(states) {
|
|
4117
|
+
const counts = emptyLifecycleCounts();
|
|
4118
|
+
for (const s of states) counts[s]++;
|
|
4119
|
+
return counts;
|
|
4120
|
+
}
|
|
4121
|
+
|
|
4122
|
+
// src/services/knowledge-artifacts.ts
|
|
4123
|
+
var ARCH_DIR2 = "knowledge";
|
|
4124
|
+
function toPosix(p2) {
|
|
4125
|
+
return p2.replace(/\\/g, "/");
|
|
4126
|
+
}
|
|
4127
|
+
function relativeTo(dir, filePath) {
|
|
4128
|
+
const base = toPosix(dir).replace(/\/+$/, "");
|
|
4129
|
+
const full = toPosix(filePath);
|
|
4130
|
+
return full.startsWith(base + "/") ? full.slice(base.length + 1) : full;
|
|
4131
|
+
}
|
|
4132
|
+
function isWorkItemArtifact(a) {
|
|
4133
|
+
return toPosix(a.filePath).includes("/delivery/work-items/") && Boolean(a.type);
|
|
4134
|
+
}
|
|
4135
|
+
function classifyLayer(posixPath) {
|
|
4136
|
+
if (posixPath.includes("/knowledge/business/")) return "business";
|
|
4137
|
+
if (posixPath.includes("/knowledge/product/")) return "product";
|
|
4138
|
+
if (posixPath.includes("/knowledge/tech/modules/") || posixPath.includes("/knowledge/modules/"))
|
|
4139
|
+
return "module";
|
|
4140
|
+
if (posixPath.includes("/knowledge/tech/")) return "tech";
|
|
4141
|
+
if (posixPath.includes("/knowledge/delivery/")) return "delivery";
|
|
4142
|
+
return "unknown";
|
|
4143
|
+
}
|
|
4144
|
+
function enrich(dir, a) {
|
|
4145
|
+
const posix = toPosix(a.filePath);
|
|
4146
|
+
const isWi = isWorkItemArtifact(a);
|
|
4147
|
+
return {
|
|
4148
|
+
...a,
|
|
4149
|
+
relPath: relativeTo(dir, a.filePath),
|
|
4150
|
+
layer: classifyLayer(posix),
|
|
4151
|
+
isWorkItem: isWi,
|
|
4152
|
+
lifecycle: isWi ? lifecycleStateOf({ status: a.status, filePath: a.filePath }) : void 0
|
|
4153
|
+
};
|
|
4154
|
+
}
|
|
4155
|
+
function discoverKnowledge(dir) {
|
|
4156
|
+
const archDir = join(dir, ARCH_DIR2);
|
|
4157
|
+
if (!exists(archDir)) return [];
|
|
4158
|
+
return readArtifacts(archDir).map((a) => enrich(dir, a));
|
|
4159
|
+
}
|
|
4160
|
+
function discoverWorkItems(dir) {
|
|
4161
|
+
return discoverKnowledge(dir).filter((a) => a.isWorkItem);
|
|
4162
|
+
}
|
|
4163
|
+
|
|
4050
4164
|
// src/core/diff-analysis.ts
|
|
4051
4165
|
import { minimatch } from "minimatch";
|
|
4052
4166
|
function normalizePath(p2) {
|
|
@@ -4439,7 +4553,7 @@ function collectMatchedDomains(matchedArtifactDomains) {
|
|
|
4439
4553
|
}
|
|
4440
4554
|
|
|
4441
4555
|
// src/commands/guard.ts
|
|
4442
|
-
var
|
|
4556
|
+
var ARCH_DIR3 = "knowledge";
|
|
4443
4557
|
var CONFIG_PATH2 = ".kaddo/config.yml";
|
|
4444
4558
|
function loadConfig2(dir) {
|
|
4445
4559
|
const configPath = join(dir, CONFIG_PATH2);
|
|
@@ -4583,12 +4697,12 @@ async function runGuard(opts = {}) {
|
|
|
4583
4697
|
console.log("kaddo guard: no modified files detected.");
|
|
4584
4698
|
return;
|
|
4585
4699
|
}
|
|
4586
|
-
const archDir = join(dir,
|
|
4700
|
+
const archDir = join(dir, ARCH_DIR3);
|
|
4587
4701
|
if (!exists(archDir)) {
|
|
4588
4702
|
console.log("kaddo guard: no knowledge/ directory found. Run `kaddo init` first.");
|
|
4589
4703
|
return;
|
|
4590
4704
|
}
|
|
4591
|
-
const artifacts =
|
|
4705
|
+
const artifacts = discoverKnowledge(dir);
|
|
4592
4706
|
const result = analyzeGuard(touchedFiles, artifacts, silentWithoutOwnership);
|
|
4593
4707
|
const pluginSignals = runPlugins(plugins, currentFiles, (filePath) => {
|
|
4594
4708
|
const abs = join(dir, filePath);
|
|
@@ -4725,7 +4839,7 @@ var STRUCTURED_TYPES = {
|
|
|
4725
4839
|
]),
|
|
4726
4840
|
Delivery: /* @__PURE__ */ new Set([])
|
|
4727
4841
|
};
|
|
4728
|
-
var
|
|
4842
|
+
var WORK_ITEM_TYPES2 = /* @__PURE__ */ new Set([
|
|
4729
4843
|
"work-item",
|
|
4730
4844
|
"feature",
|
|
4731
4845
|
"bugfix",
|
|
@@ -4738,7 +4852,7 @@ function layerForType(type) {
|
|
|
4738
4852
|
if (type === CONSOLIDATED_TYPE.Product || STRUCTURED_TYPES.Product.has(type)) return "Product";
|
|
4739
4853
|
if (type === CONSOLIDATED_TYPE.Tech || STRUCTURED_TYPES.Tech.has(type) || type === "knowledge")
|
|
4740
4854
|
return "Tech";
|
|
4741
|
-
if (type === "roadmap" ||
|
|
4855
|
+
if (type === "roadmap" || WORK_ITEM_TYPES2.has(type)) return "Delivery";
|
|
4742
4856
|
return null;
|
|
4743
4857
|
}
|
|
4744
4858
|
function layerFromPath(filePath) {
|
|
@@ -4769,7 +4883,7 @@ function discoverLayers(dir) {
|
|
|
4769
4883
|
slot.detected.add(basename(a.filePath));
|
|
4770
4884
|
if (layer === "Delivery") {
|
|
4771
4885
|
if (type === "roadmap" || basename(a.filePath) === "roadmap.md") slot.hasRoadmap = true;
|
|
4772
|
-
if (
|
|
4886
|
+
if (WORK_ITEM_TYPES2.has(type) || a.filePath.replace(/\\/g, "/").includes("/delivery/work-items/")) {
|
|
4773
4887
|
if (a.filePath.replace(/\\/g, "/").includes("/delivery/work-items/")) slot.hasWorkItem = true;
|
|
4774
4888
|
}
|
|
4775
4889
|
continue;
|
|
@@ -4857,54 +4971,8 @@ function renderLayersMarkdown(layers) {
|
|
|
4857
4971
|
return lines.join("\n").trimEnd();
|
|
4858
4972
|
}
|
|
4859
4973
|
|
|
4860
|
-
// src/core/lifecycle.ts
|
|
4861
|
-
var LIFECYCLE_STATES = [
|
|
4862
|
-
"draft",
|
|
4863
|
-
"ready",
|
|
4864
|
-
"in-progress",
|
|
4865
|
-
"blocked",
|
|
4866
|
-
"completed",
|
|
4867
|
-
"archived"
|
|
4868
|
-
];
|
|
4869
|
-
var ACTIVE_STATES = ["draft", "ready", "in-progress", "blocked"];
|
|
4870
|
-
var LEGACY_STATUS_MAP = {
|
|
4871
|
-
done: "completed",
|
|
4872
|
-
cancelled: "archived",
|
|
4873
|
-
canceled: "archived",
|
|
4874
|
-
"in-progress": "in-progress"
|
|
4875
|
-
};
|
|
4876
|
-
var STATE_SET = new Set(LIFECYCLE_STATES);
|
|
4877
|
-
function isLifecycleState(s) {
|
|
4878
|
-
return STATE_SET.has(s);
|
|
4879
|
-
}
|
|
4880
|
-
function isActiveState(s) {
|
|
4881
|
-
return ACTIVE_STATES.includes(s);
|
|
4882
|
-
}
|
|
4883
|
-
function lifecycleFolderOf(filePath) {
|
|
4884
|
-
const p2 = filePath.replace(/\\/g, "/");
|
|
4885
|
-
const m = p2.match(/\/delivery\/work-items\/([^/]+)\//);
|
|
4886
|
-
if (m && isLifecycleState(m[1])) return m[1];
|
|
4887
|
-
return null;
|
|
4888
|
-
}
|
|
4889
|
-
function lifecycleStateOf(item) {
|
|
4890
|
-
const status = (item.status ?? "").trim().toLowerCase();
|
|
4891
|
-
if (isLifecycleState(status)) return status;
|
|
4892
|
-
if (status && LEGACY_STATUS_MAP[status]) return LEGACY_STATUS_MAP[status];
|
|
4893
|
-
const folder = lifecycleFolderOf(item.filePath ?? "");
|
|
4894
|
-
if (folder) return folder;
|
|
4895
|
-
return "ready";
|
|
4896
|
-
}
|
|
4897
|
-
function emptyLifecycleCounts() {
|
|
4898
|
-
return Object.fromEntries(LIFECYCLE_STATES.map((s) => [s, 0]));
|
|
4899
|
-
}
|
|
4900
|
-
function lifecycleCounts(states) {
|
|
4901
|
-
const counts = emptyLifecycleCounts();
|
|
4902
|
-
for (const s of states) counts[s]++;
|
|
4903
|
-
return counts;
|
|
4904
|
-
}
|
|
4905
|
-
|
|
4906
4974
|
// src/core/project-explain.ts
|
|
4907
|
-
var
|
|
4975
|
+
var ARCH_DIR4 = "knowledge";
|
|
4908
4976
|
function first(values) {
|
|
4909
4977
|
if (Array.isArray(values)) {
|
|
4910
4978
|
const v = values.find((x) => typeof x === "string" && x);
|
|
@@ -4935,7 +5003,7 @@ function loadScan(dir) {
|
|
|
4935
5003
|
}
|
|
4936
5004
|
}
|
|
4937
5005
|
function hasAgents(dir) {
|
|
4938
|
-
const agentsDir = join(dir,
|
|
5006
|
+
const agentsDir = join(dir, ARCH_DIR4, "agents");
|
|
4939
5007
|
if (!exists(agentsDir)) return false;
|
|
4940
5008
|
function hasAgentMd(d) {
|
|
4941
5009
|
for (const e of readDir(d)) {
|
|
@@ -4972,7 +5040,7 @@ function buildProjectExplanation(dir) {
|
|
|
4972
5040
|
const layerStatus = (name) => layers.find((l) => l.layer === name)?.status ?? "Missing";
|
|
4973
5041
|
const knowledge = {
|
|
4974
5042
|
hasScan: scan2 !== null,
|
|
4975
|
-
hasInventory: exists(join(dir,
|
|
5043
|
+
hasInventory: exists(join(dir, ARCH_DIR4, "inventory.md")),
|
|
4976
5044
|
hasContextPack: exists(join(dir, ".kaddo", "context-pack.md")),
|
|
4977
5045
|
hasUnderstand: exists(join(dir, ".kaddo", "understand.md")),
|
|
4978
5046
|
hasCapabilities: layerStatus("Product") !== "Missing",
|
|
@@ -4980,15 +5048,11 @@ function buildProjectExplanation(dir) {
|
|
|
4980
5048
|
hasRoadmap: layerStatus("Delivery") !== "Missing",
|
|
4981
5049
|
hasAgents: hasAgents(dir)
|
|
4982
5050
|
};
|
|
4983
|
-
const
|
|
4984
|
-
const allArtifacts = exists(archDir) ? readArtifacts(archDir) : [];
|
|
4985
|
-
const workItemArtifacts = allArtifacts.filter((a) => {
|
|
4986
|
-
const p2 = a.filePath.replace(/\\/g, "/");
|
|
4987
|
-
return p2.includes("/delivery/work-items/") && Boolean(a.type);
|
|
4988
|
-
});
|
|
5051
|
+
const workItemArtifacts = discoverWorkItems(dir);
|
|
4989
5052
|
const items = workItemArtifacts.map((a) => ({
|
|
4990
5053
|
id: a.id || a.title,
|
|
4991
5054
|
title: a.title,
|
|
5055
|
+
type: a.type,
|
|
4992
5056
|
status: a.status,
|
|
4993
5057
|
lifecycle: lifecycleStateOf({ status: a.status, filePath: a.filePath }),
|
|
4994
5058
|
initiative: a.initiative,
|
|
@@ -4997,6 +5061,11 @@ function buildProjectExplanation(dir) {
|
|
|
4997
5061
|
domains: a.domains
|
|
4998
5062
|
}));
|
|
4999
5063
|
const byState = lifecycleCounts(items.map((i) => i.lifecycle));
|
|
5064
|
+
const byType = {};
|
|
5065
|
+
for (const i of items) {
|
|
5066
|
+
const t = i.type || "unknown";
|
|
5067
|
+
byType[t] = (byType[t] ?? 0) + 1;
|
|
5068
|
+
}
|
|
5000
5069
|
const initiativeMap = /* @__PURE__ */ new Map();
|
|
5001
5070
|
for (const i of items) {
|
|
5002
5071
|
const name = i.initiative || "Unassigned";
|
|
@@ -5010,6 +5079,7 @@ function buildProjectExplanation(dir) {
|
|
|
5010
5079
|
done: items.filter((i) => i.status === "done").length,
|
|
5011
5080
|
cancelled: items.filter((i) => i.status === "cancelled").length,
|
|
5012
5081
|
byState,
|
|
5082
|
+
byType,
|
|
5013
5083
|
initiatives,
|
|
5014
5084
|
items
|
|
5015
5085
|
};
|
|
@@ -5020,7 +5090,7 @@ function buildProjectExplanation(dir) {
|
|
|
5020
5090
|
workItemsMissingOwnership: items.length - withOwnership
|
|
5021
5091
|
};
|
|
5022
5092
|
const domains = [...new Set(workItemArtifacts.flatMap((a) => a.domains))].filter(Boolean);
|
|
5023
|
-
const roadmapPath = join(dir,
|
|
5093
|
+
const roadmapPath = join(dir, ARCH_DIR4, "delivery", "roadmap.md");
|
|
5024
5094
|
const roadmapMd = exists(roadmapPath) ? readFile(roadmapPath) : null;
|
|
5025
5095
|
const roadmap = roadmapStats(roadmapMd, items.length);
|
|
5026
5096
|
const mappedModules = loadMappedModules(dir);
|
|
@@ -5079,6 +5149,10 @@ function buildProjectExplanation(dir) {
|
|
|
5079
5149
|
function stateLabel(state) {
|
|
5080
5150
|
return state === "pre-ai" ? "pre-ai" : state;
|
|
5081
5151
|
}
|
|
5152
|
+
function typeLabel(type) {
|
|
5153
|
+
const cap = type.charAt(0).toUpperCase() + type.slice(1);
|
|
5154
|
+
return cap.endsWith("s") ? cap : `${cap}s`;
|
|
5155
|
+
}
|
|
5082
5156
|
var LIFECYCLE_LABEL = {
|
|
5083
5157
|
draft: "Draft",
|
|
5084
5158
|
ready: "Ready",
|
|
@@ -5140,6 +5214,12 @@ function renderExplanationHuman(exp) {
|
|
|
5140
5214
|
lines.push(`- ${LIFECYCLE_LABEL[s]}: ${exp.workItems.byState[s]}`);
|
|
5141
5215
|
}
|
|
5142
5216
|
lines.push("");
|
|
5217
|
+
const typeEntries = Object.entries(exp.workItems.byType).sort((a, b) => b[1] - a[1]);
|
|
5218
|
+
if (typeEntries.length > 0) {
|
|
5219
|
+
lines.push("## Work Items by Type");
|
|
5220
|
+
for (const [t, n] of typeEntries) lines.push(`- ${typeLabel(t)}: ${n}`);
|
|
5221
|
+
lines.push("");
|
|
5222
|
+
}
|
|
5143
5223
|
const grouped = exp.workItems.initiatives.filter(
|
|
5144
5224
|
(g) => LIFECYCLE_STATES.some((s) => g.states[s] > 0)
|
|
5145
5225
|
);
|
|
@@ -5204,10 +5284,10 @@ function renderExplanationAgent(exp) {
|
|
|
5204
5284
|
}
|
|
5205
5285
|
|
|
5206
5286
|
// src/commands/explain.ts
|
|
5207
|
-
var
|
|
5287
|
+
var ARCH_DIR5 = "knowledge";
|
|
5208
5288
|
var CONFIG_PATH3 = ".kaddo/config.yml";
|
|
5209
5289
|
function readKnowledge(dir) {
|
|
5210
|
-
const knowledgePath = join(dir,
|
|
5290
|
+
const knowledgePath = join(dir, ARCH_DIR5, "knowledge.md");
|
|
5211
5291
|
if (!exists(knowledgePath)) return null;
|
|
5212
5292
|
try {
|
|
5213
5293
|
const raw = readFile(knowledgePath);
|
|
@@ -5218,7 +5298,7 @@ function readKnowledge(dir) {
|
|
|
5218
5298
|
}
|
|
5219
5299
|
}
|
|
5220
5300
|
function readRoadmap(dir) {
|
|
5221
|
-
const roadmapPath = join(dir,
|
|
5301
|
+
const roadmapPath = join(dir, ARCH_DIR5, "roadmap.md");
|
|
5222
5302
|
if (!exists(roadmapPath)) return null;
|
|
5223
5303
|
try {
|
|
5224
5304
|
const raw = readFile(roadmapPath);
|
|
@@ -5367,7 +5447,7 @@ function runExplain(opts) {
|
|
|
5367
5447
|
explainProject(dir, mode);
|
|
5368
5448
|
return;
|
|
5369
5449
|
}
|
|
5370
|
-
const archDir = join(dir,
|
|
5450
|
+
const archDir = join(dir, ARCH_DIR5);
|
|
5371
5451
|
if (!exists(archDir)) {
|
|
5372
5452
|
console.error("No knowledge/ directory found. Run `kaddo init` first.");
|
|
5373
5453
|
process.exit(1);
|
|
@@ -5392,7 +5472,7 @@ function runExplain(opts) {
|
|
|
5392
5472
|
// src/core/context-pack.ts
|
|
5393
5473
|
import matter3 from "gray-matter";
|
|
5394
5474
|
var CONTEXT_PACK_VERSION = "1";
|
|
5395
|
-
var
|
|
5475
|
+
var ARCH_DIR6 = "knowledge";
|
|
5396
5476
|
function readScanJson(dir) {
|
|
5397
5477
|
const scanPath = join(dir, ".kaddo", "scan.json");
|
|
5398
5478
|
if (!exists(scanPath)) return null;
|
|
@@ -5408,7 +5488,7 @@ function firstParagraph(markdown) {
|
|
|
5408
5488
|
return para ?? "";
|
|
5409
5489
|
}
|
|
5410
5490
|
function readMarkdownSummary(dir, file) {
|
|
5411
|
-
const p2 = join(dir,
|
|
5491
|
+
const p2 = join(dir, ARCH_DIR6, file);
|
|
5412
5492
|
if (!exists(p2)) return null;
|
|
5413
5493
|
try {
|
|
5414
5494
|
return firstParagraph(readFile(p2));
|
|
@@ -5486,7 +5566,7 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
|
|
|
5486
5566
|
missing.push("Scan baseline missing. Run `kaddo scan` for better context.");
|
|
5487
5567
|
}
|
|
5488
5568
|
const detected = scanJson?.detected ?? {};
|
|
5489
|
-
const inventoryAvailable = exists(join(dir,
|
|
5569
|
+
const inventoryAvailable = exists(join(dir, ARCH_DIR6, "inventory.md"));
|
|
5490
5570
|
if (!inventoryAvailable) {
|
|
5491
5571
|
missing.push("No technical inventory found. Run `kaddo scan` to generate it.");
|
|
5492
5572
|
}
|
|
@@ -5498,8 +5578,7 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
|
|
|
5498
5578
|
if (!roadmapSummary) {
|
|
5499
5579
|
missing.push("No roadmap baseline found.");
|
|
5500
5580
|
}
|
|
5501
|
-
const
|
|
5502
|
-
const allArtifacts = exists(archPath) ? readArtifacts(archPath) : [];
|
|
5581
|
+
const allArtifacts = discoverKnowledge(dir);
|
|
5503
5582
|
const workItems = allArtifacts.filter(
|
|
5504
5583
|
(a) => isDeliveryWorkItem(a) && isActiveState(lifecycleStateOf({ status: a.status, filePath: a.filePath }))
|
|
5505
5584
|
);
|
|
@@ -5507,11 +5586,18 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
|
|
|
5507
5586
|
missing.push("No work items found.");
|
|
5508
5587
|
}
|
|
5509
5588
|
const state = config.project.state;
|
|
5510
|
-
const roadmapPath = join(dir,
|
|
5589
|
+
const roadmapPath = join(dir, ARCH_DIR6, "delivery", "roadmap.md");
|
|
5511
5590
|
const materialized = allArtifacts.filter(
|
|
5512
5591
|
(a) => a.filePath.replace(/\\/g, "/").includes("/delivery/work-items/") && Boolean(a.type)
|
|
5513
5592
|
).length;
|
|
5514
5593
|
const roadmap = roadmapStats(exists(roadmapPath) ? readFile(roadmapPath) : null, materialized);
|
|
5594
|
+
const deliveryMix = {};
|
|
5595
|
+
for (const a of allArtifacts) {
|
|
5596
|
+
const p2 = a.filePath.replace(/\\/g, "/");
|
|
5597
|
+
if (!a.type || !p2.includes("/delivery/work-items/")) continue;
|
|
5598
|
+
if (!isActiveState(lifecycleStateOf({ status: a.status, filePath: a.filePath }))) continue;
|
|
5599
|
+
deliveryMix[a.type] = (deliveryMix[a.type] ?? 0) + 1;
|
|
5600
|
+
}
|
|
5515
5601
|
const mappedModules = loadMappedModules(dir);
|
|
5516
5602
|
const layers = knowledgeLayers(dir);
|
|
5517
5603
|
return {
|
|
@@ -5542,6 +5628,7 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
|
|
|
5542
5628
|
},
|
|
5543
5629
|
layers,
|
|
5544
5630
|
roadmap,
|
|
5631
|
+
deliveryMix,
|
|
5545
5632
|
mappedModules,
|
|
5546
5633
|
missing,
|
|
5547
5634
|
handoff: {
|
|
@@ -5643,6 +5730,16 @@ function renderContextPack(pack) {
|
|
|
5643
5730
|
} else {
|
|
5644
5731
|
parts.push("No active work items found.\n");
|
|
5645
5732
|
}
|
|
5733
|
+
const mixEntries = Object.entries(pack.deliveryMix).sort((a, b) => b[1] - a[1]);
|
|
5734
|
+
if (mixEntries.length > 0) {
|
|
5735
|
+
parts.push("## Delivery Mix\n");
|
|
5736
|
+
parts.push("Active Work Items by type:\n");
|
|
5737
|
+
const label = (t) => {
|
|
5738
|
+
const c = t.charAt(0).toUpperCase() + t.slice(1);
|
|
5739
|
+
return c.endsWith("s") ? c : `${c}s`;
|
|
5740
|
+
};
|
|
5741
|
+
parts.push(mixEntries.map(([t, n]) => `- ${label(t)}: ${n}`).join("\n") + "\n");
|
|
5742
|
+
}
|
|
5646
5743
|
parts.push("## Artifacts and Ownership\n");
|
|
5647
5744
|
if (knowledge.artifacts.length > 0) {
|
|
5648
5745
|
const lines = knowledge.artifacts.map(
|
|
@@ -5883,19 +5980,12 @@ import { parse as parseYaml9 } from "yaml";
|
|
|
5883
5980
|
function slugify2(s) {
|
|
5884
5981
|
return s.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
5885
5982
|
}
|
|
5886
|
-
function isWorkItem(a) {
|
|
5887
|
-
return a.filePath.replace(/\\/g, "/").includes("/delivery/work-items/") && Boolean(a.type);
|
|
5888
|
-
}
|
|
5889
5983
|
function toActive(a) {
|
|
5890
5984
|
const id = a.id || a.title || "WI";
|
|
5891
5985
|
return { id, title: a.title || id, type: a.type, slug: slugify2(a.title || id) };
|
|
5892
5986
|
}
|
|
5893
5987
|
function activeWorkItems(dir) {
|
|
5894
|
-
|
|
5895
|
-
if (!exists(archDir)) return [];
|
|
5896
|
-
return readArtifacts(archDir).filter(
|
|
5897
|
-
(a) => isWorkItem(a) && lifecycleStateOf({ status: a.status, filePath: a.filePath }) === "in-progress"
|
|
5898
|
-
).map(toActive);
|
|
5988
|
+
return discoverWorkItems(dir).filter((a) => a.lifecycle === "in-progress").map(toActive);
|
|
5899
5989
|
}
|
|
5900
5990
|
function branchPrefix(type) {
|
|
5901
5991
|
switch (type) {
|
|
@@ -6198,9 +6288,9 @@ function classify(declaredType, declaredLevel, touchedFiles) {
|
|
|
6198
6288
|
}
|
|
6199
6289
|
|
|
6200
6290
|
// src/commands/classify.ts
|
|
6201
|
-
var
|
|
6291
|
+
var ARCH_DIR7 = "knowledge";
|
|
6202
6292
|
function findActiveWorkItem(dir) {
|
|
6203
|
-
const archDir = join(dir,
|
|
6293
|
+
const archDir = join(dir, ARCH_DIR7);
|
|
6204
6294
|
if (!exists(archDir)) return null;
|
|
6205
6295
|
const artifacts = readArtifacts(archDir);
|
|
6206
6296
|
return artifacts.filter((a) => a.status === "in-progress" && a.type !== "current-state" && a.type !== "roadmap").sort((a, b) => {
|
|
@@ -6266,7 +6356,7 @@ async function runClassify(opts = {}) {
|
|
|
6266
6356
|
|
|
6267
6357
|
// src/commands/status.ts
|
|
6268
6358
|
import { parse as parseYaml10 } from "yaml";
|
|
6269
|
-
var
|
|
6359
|
+
var ARCH_DIR8 = "knowledge";
|
|
6270
6360
|
var CONFIG_PATH4 = ".kaddo/config.yml";
|
|
6271
6361
|
function loadConfig3(dir) {
|
|
6272
6362
|
const p2 = join(dir, CONFIG_PATH4);
|
|
@@ -6279,12 +6369,12 @@ function loadConfig3(dir) {
|
|
|
6279
6369
|
}
|
|
6280
6370
|
function runStatus() {
|
|
6281
6371
|
const dir = cwd();
|
|
6282
|
-
if (!exists(join(dir,
|
|
6372
|
+
if (!exists(join(dir, ARCH_DIR8))) {
|
|
6283
6373
|
console.error("No knowledge/ directory found. Run `kaddo init` first.");
|
|
6284
6374
|
process.exit(1);
|
|
6285
6375
|
}
|
|
6286
6376
|
const config = loadConfig3(dir);
|
|
6287
|
-
const artifacts = readArtifacts(join(dir,
|
|
6377
|
+
const artifacts = readArtifacts(join(dir, ARCH_DIR8));
|
|
6288
6378
|
const ignores = loadIgnores(dir);
|
|
6289
6379
|
const workItems = artifacts.filter(
|
|
6290
6380
|
(a) => a.type !== "current-state" && a.type !== "roadmap"
|
|
@@ -6330,7 +6420,7 @@ function runStatus() {
|
|
|
6330
6420
|
|
|
6331
6421
|
// src/commands/learn.ts
|
|
6332
6422
|
import matter4 from "gray-matter";
|
|
6333
|
-
var
|
|
6423
|
+
var ARCH_DIR9 = "knowledge";
|
|
6334
6424
|
var WORK_ITEMS_DIR2 = "knowledge/delivery/work-items";
|
|
6335
6425
|
function findWorkItemFile(dir, id) {
|
|
6336
6426
|
const wiDir = join(dir, WORK_ITEMS_DIR2);
|
|
@@ -6371,12 +6461,12 @@ ${learning.trim()}
|
|
|
6371
6461
|
}
|
|
6372
6462
|
async function runLearn(artifactId) {
|
|
6373
6463
|
const dir = cwd();
|
|
6374
|
-
if (!exists(join(dir,
|
|
6464
|
+
if (!exists(join(dir, ARCH_DIR9))) {
|
|
6375
6465
|
console.error("No knowledge/ directory found. Run `kaddo init` first.");
|
|
6376
6466
|
process.exit(1);
|
|
6377
6467
|
}
|
|
6378
6468
|
intro2("kaddo learn");
|
|
6379
|
-
const artifacts = readArtifacts(join(dir,
|
|
6469
|
+
const artifacts = readArtifacts(join(dir, ARCH_DIR9));
|
|
6380
6470
|
const inProgress = artifacts.filter(
|
|
6381
6471
|
(a) => a.status === "in-progress" && a.type !== "current-state" && a.type !== "roadmap"
|
|
6382
6472
|
);
|
|
@@ -6419,7 +6509,7 @@ async function runLearn(artifactId) {
|
|
|
6419
6509
|
}
|
|
6420
6510
|
|
|
6421
6511
|
// src/commands/history.ts
|
|
6422
|
-
var
|
|
6512
|
+
var ARCH_DIR10 = "knowledge";
|
|
6423
6513
|
function formatRow(a) {
|
|
6424
6514
|
const id = (a.id || "").padEnd(8);
|
|
6425
6515
|
const type = a.type.padEnd(18);
|
|
@@ -6430,11 +6520,11 @@ function formatRow(a) {
|
|
|
6430
6520
|
}
|
|
6431
6521
|
function runHistory(opts = {}) {
|
|
6432
6522
|
const dir = cwd();
|
|
6433
|
-
if (!exists(join(dir,
|
|
6523
|
+
if (!exists(join(dir, ARCH_DIR10))) {
|
|
6434
6524
|
console.error("No knowledge/ directory found. Run `kaddo init` first.");
|
|
6435
6525
|
process.exit(1);
|
|
6436
6526
|
}
|
|
6437
|
-
let artifacts = readArtifacts(join(dir,
|
|
6527
|
+
let artifacts = readArtifacts(join(dir, ARCH_DIR10)).filter(
|
|
6438
6528
|
(a) => a.type !== "current-state" && a.type !== "roadmap"
|
|
6439
6529
|
);
|
|
6440
6530
|
if (opts.domain) {
|
|
@@ -6595,42 +6685,23 @@ function runAdd(moduleName, opts = {}, dir = cwd()) {
|
|
|
6595
6685
|
|
|
6596
6686
|
// src/core/ownership-suggest.ts
|
|
6597
6687
|
import matter5 from "gray-matter";
|
|
6598
|
-
var WORK_ITEMS_DIR3 = "knowledge/delivery/work-items";
|
|
6599
6688
|
var SCAN_PATH = ".kaddo/scan.json";
|
|
6600
6689
|
function toStringArray3(value) {
|
|
6601
6690
|
return Array.isArray(value) ? value.map((v) => String(v)).filter(Boolean) : [];
|
|
6602
6691
|
}
|
|
6603
|
-
function parseOwnershipArtifact(filePath, relPath, raw) {
|
|
6604
|
-
try {
|
|
6605
|
-
const { data } = matter5(raw);
|
|
6606
|
-
const codeGlobs = toStringArray3(data.code);
|
|
6607
|
-
return {
|
|
6608
|
-
filePath,
|
|
6609
|
-
relPath: relPath.split("\\").join("/"),
|
|
6610
|
-
id: String(data.id ?? ""),
|
|
6611
|
-
title: String(data.title ?? ""),
|
|
6612
|
-
type: String(data.type ?? ""),
|
|
6613
|
-
domains: toStringArray3(data.domains),
|
|
6614
|
-
capabilities: toStringArray3(data.capabilities),
|
|
6615
|
-
codeGlobs,
|
|
6616
|
-
source: data.source ? String(data.source) : void 0,
|
|
6617
|
-
hasOwnership: codeGlobs.length > 0
|
|
6618
|
-
};
|
|
6619
|
-
} catch {
|
|
6620
|
-
return null;
|
|
6621
|
-
}
|
|
6622
|
-
}
|
|
6623
6692
|
function findWorkItemArtifacts(dir) {
|
|
6624
|
-
|
|
6625
|
-
|
|
6626
|
-
|
|
6627
|
-
|
|
6628
|
-
|
|
6629
|
-
|
|
6630
|
-
|
|
6631
|
-
|
|
6632
|
-
|
|
6633
|
-
|
|
6693
|
+
return discoverWorkItems(dir).map((a) => ({
|
|
6694
|
+
filePath: a.filePath,
|
|
6695
|
+
relPath: a.relPath,
|
|
6696
|
+
id: a.id,
|
|
6697
|
+
title: a.title,
|
|
6698
|
+
type: a.type,
|
|
6699
|
+
domains: a.domains,
|
|
6700
|
+
capabilities: a.capabilities,
|
|
6701
|
+
codeGlobs: a.codeGlobs,
|
|
6702
|
+
source: a.source || void 0,
|
|
6703
|
+
hasOwnership: a.codeGlobs.length > 0
|
|
6704
|
+
}));
|
|
6634
6705
|
}
|
|
6635
6706
|
function artifactsMissingOwnership(artifacts) {
|
|
6636
6707
|
return artifacts.filter((a) => !a.hasOwnership);
|
|
@@ -6774,16 +6845,24 @@ async function runOwnersSuggest() {
|
|
|
6774
6845
|
intro2("kaddo owners suggest");
|
|
6775
6846
|
const artifacts = findWorkItemArtifacts(dir);
|
|
6776
6847
|
if (artifacts.length === 0) {
|
|
6777
|
-
log2.warn(
|
|
6848
|
+
log2.warn(
|
|
6849
|
+
"No Work Items found under knowledge/delivery/work-items/ (searched recursively, including draft/ ready/ in-progress/ \u2026)."
|
|
6850
|
+
);
|
|
6851
|
+
log2.info("Create one with `kaddo create` or `kaddo create --from roadmap`.");
|
|
6778
6852
|
outro2("Nothing to do.");
|
|
6779
6853
|
return;
|
|
6780
6854
|
}
|
|
6781
6855
|
const missing = artifactsMissingOwnership(artifacts);
|
|
6782
6856
|
if (missing.length === 0) {
|
|
6783
|
-
log2.
|
|
6857
|
+
log2.success(
|
|
6858
|
+
`Found ${artifacts.length} Work Item${artifacts.length === 1 ? "" : "s"}; all already declare code ownership.`
|
|
6859
|
+
);
|
|
6784
6860
|
outro2("Nothing to do.");
|
|
6785
6861
|
return;
|
|
6786
6862
|
}
|
|
6863
|
+
log2.info(
|
|
6864
|
+
`Found ${artifacts.length} Work Item${artifacts.length === 1 ? "" : "s"}; ${missing.length} missing code ownership.`
|
|
6865
|
+
);
|
|
6787
6866
|
const relPath = missing.length === 1 ? missing[0].relPath : await select2({
|
|
6788
6867
|
message: "Which artifact needs ownership?",
|
|
6789
6868
|
options: missing.map((a) => ({
|
|
@@ -8745,7 +8824,7 @@ program.command("scan").description("Detect project stack and suggest domains").
|
|
|
8745
8824
|
program.command("bootstrap").description("Build the initial knowledge base for a new project (Business \u2192 Product \u2192 Tech \u2192 Delivery)").action(async () => {
|
|
8746
8825
|
await runBootstrap();
|
|
8747
8826
|
});
|
|
8748
|
-
program.command("create [type]").description("Create a work item (feature, bugfix, hotfix, spike). Use --from roadmap to create from a roadmap candidate.").option("--from <source>", "Create from a source artifact (currently: roadmap)").action(async (type, opts) => {
|
|
8827
|
+
program.command("create [type]").description("Create a work item (feature, bugfix, hotfix, spike, chore). Use --from roadmap to create from a roadmap candidate.").option("--from <source>", "Create from a source artifact (currently: roadmap)").action(async (type, opts) => {
|
|
8749
8828
|
await runCreate(type ?? "", opts);
|
|
8750
8829
|
});
|
|
8751
8830
|
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) => {
|