@kaddo/mcp 3.61.0 → 3.63.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +692 -34
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -168,7 +168,8 @@ var DERIVED_WRITE_FILES = /* @__PURE__ */ new Set([
|
|
|
168
168
|
".kaddo/graph.json",
|
|
169
169
|
".kaddo/graph.mmd",
|
|
170
170
|
".kaddo/graph-hints.md",
|
|
171
|
-
".kaddo/graph-hints.json"
|
|
171
|
+
".kaddo/graph-hints.json",
|
|
172
|
+
".kaddo/modules.yml"
|
|
172
173
|
]);
|
|
173
174
|
var DERIVED_EXPORTS_RE = /^\.kaddo\/exports\/[^/]+\.capsule\.(md|json)$/;
|
|
174
175
|
var DERIVED_REPORTS_RE = /^\.kaddo\/reports\/[^/]+\.(md|json)$/;
|
|
@@ -447,10 +448,14 @@ var configSchema = z.object({
|
|
|
447
448
|
team: z.object({
|
|
448
449
|
size: teamSizeSchema.default(DEFAULTS.teamSize)
|
|
449
450
|
}).passthrough().default({}),
|
|
451
|
+
system: z.object({
|
|
452
|
+
name: z.string().optional()
|
|
453
|
+
}).passthrough().optional(),
|
|
450
454
|
multirepo: z.object({
|
|
451
455
|
role: multirepoRoleSchema,
|
|
452
456
|
modules_file: z.string().optional(),
|
|
453
|
-
parent_system: z.string().optional()
|
|
457
|
+
parent_system: z.string().optional(),
|
|
458
|
+
workspace_roots: z.array(z.string()).optional()
|
|
454
459
|
}).passthrough().optional(),
|
|
455
460
|
module: z.object({
|
|
456
461
|
id: z.string().optional()
|
|
@@ -475,8 +480,8 @@ function loadConfig(dir) {
|
|
|
475
480
|
const parsed = configSchema.safeParse(raw ?? {});
|
|
476
481
|
if (!parsed.success) {
|
|
477
482
|
const issues = parsed.error.issues.map((i) => {
|
|
478
|
-
const
|
|
479
|
-
return
|
|
483
|
+
const path4 = i.path.join(".");
|
|
484
|
+
return path4 ? ` - ${path4}: ${i.message}` : ` - ${i.message}`;
|
|
480
485
|
}).join("\n");
|
|
481
486
|
throw new ConfigError(`Invalid .kaddo/config.yml:
|
|
482
487
|
${issues}`);
|
|
@@ -489,6 +494,9 @@ function projectRole(config) {
|
|
|
489
494
|
function isModule(config) {
|
|
490
495
|
return projectRole(config) === "module";
|
|
491
496
|
}
|
|
497
|
+
function isCore(config) {
|
|
498
|
+
return projectRole(config) === "core";
|
|
499
|
+
}
|
|
492
500
|
|
|
493
501
|
// ../cli/src/services/artifact-reader.ts
|
|
494
502
|
import matter3 from "gray-matter";
|
|
@@ -790,7 +798,7 @@ function assessPhase(input) {
|
|
|
790
798
|
|
|
791
799
|
// ../cli/src/core/capsule.ts
|
|
792
800
|
import matter4 from "gray-matter";
|
|
793
|
-
import { parse as
|
|
801
|
+
import { parse as parseYaml7, stringify as stringifyYaml3 } from "yaml";
|
|
794
802
|
|
|
795
803
|
// ../cli/src/services/owners.ts
|
|
796
804
|
import { parse as parseYaml3 } from "yaml";
|
|
@@ -829,11 +837,11 @@ function moduleArtifactCoverage(dir, id) {
|
|
|
829
837
|
};
|
|
830
838
|
}
|
|
831
839
|
function loadMappedModules(dir) {
|
|
832
|
-
const
|
|
833
|
-
if (!exists(
|
|
840
|
+
const path4 = join(dir, DESCRIPTOR_PATH);
|
|
841
|
+
if (!exists(path4)) return [];
|
|
834
842
|
let parsed;
|
|
835
843
|
try {
|
|
836
|
-
parsed = parseYaml4(readFile(
|
|
844
|
+
parsed = parseYaml4(readFile(path4));
|
|
837
845
|
} catch {
|
|
838
846
|
return [];
|
|
839
847
|
}
|
|
@@ -847,7 +855,7 @@ function loadMappedModules(dir) {
|
|
|
847
855
|
modules.push({
|
|
848
856
|
id,
|
|
849
857
|
name: typeof m2.name === "string" ? m2.name : void 0,
|
|
850
|
-
repoPath: typeof m2.repoPath === "string" ? m2.repoPath : "",
|
|
858
|
+
repoPath: typeof m2.repoPath === "string" ? m2.repoPath : typeof m2.path === "string" ? m2.path : "",
|
|
851
859
|
type: typeof m2.type === "string" ? m2.type : void 0,
|
|
852
860
|
mainTechnology: typeof m2.mainTechnology === "string" ? m2.mainTechnology : void 0,
|
|
853
861
|
owner: typeof m2.owner === "string" ? m2.owner : void 0,
|
|
@@ -896,13 +904,15 @@ function detectModuleStatus(coreDir, repoPath) {
|
|
|
896
904
|
status: "configured",
|
|
897
905
|
configured: true,
|
|
898
906
|
role: "module",
|
|
899
|
-
moduleContext: exists(join(moduleDir, "knowledge", "module", "module-context.md")),
|
|
907
|
+
moduleContext: exists(join(moduleDir, "knowledge", "tech", "module", "module-context.md")) || exists(join(moduleDir, "knowledge", "module", "module-context.md")),
|
|
900
908
|
currentState: exists(join(moduleDir, "knowledge", "tech", "current-state.md")),
|
|
901
909
|
codebase: exists(join(moduleDir, "knowledge", "tech", "codebase.md"))
|
|
902
910
|
};
|
|
903
911
|
}
|
|
904
912
|
function readModuleContext(coreDir, repoPath) {
|
|
905
|
-
const
|
|
913
|
+
const newPath = join(coreDir, repoPath, "knowledge", "tech", "module", "module-context.md");
|
|
914
|
+
const legacyPath = join(coreDir, repoPath, "knowledge", "module", "module-context.md");
|
|
915
|
+
const p = exists(newPath) ? newPath : legacyPath;
|
|
906
916
|
if (!exists(p)) return null;
|
|
907
917
|
try {
|
|
908
918
|
return readFile(p);
|
|
@@ -1042,7 +1052,7 @@ var Q2 = o("\u25B2", "!");
|
|
|
1042
1052
|
var ee = o("\u25A0", "x");
|
|
1043
1053
|
|
|
1044
1054
|
// ../cli/src/commands/modules-map.ts
|
|
1045
|
-
import { parse as
|
|
1055
|
+
import { parse as parseYaml6, stringify as stringifyYaml2 } from "yaml";
|
|
1046
1056
|
|
|
1047
1057
|
// ../cli/src/templates/registry.ts
|
|
1048
1058
|
var QUALITY = "## Quality checklist";
|
|
@@ -2153,6 +2163,9 @@ ${QUALITY}
|
|
|
2153
2163
|
- [ ] The next step is clear.
|
|
2154
2164
|
`;
|
|
2155
2165
|
|
|
2166
|
+
// ../cli/src/commands/modules-discover.ts
|
|
2167
|
+
import { parse as parseYaml5, stringify as stringifyYaml } from "yaml";
|
|
2168
|
+
|
|
2156
2169
|
// ../cli/src/core/capsule.ts
|
|
2157
2170
|
var KNOWLEDGE2 = "knowledge";
|
|
2158
2171
|
function today() {
|
|
@@ -2252,7 +2265,7 @@ function loadExternalRegistry(dir) {
|
|
|
2252
2265
|
const p = join(dir, EXTERNAL_PATH);
|
|
2253
2266
|
if (!exists(p)) return [];
|
|
2254
2267
|
try {
|
|
2255
|
-
const parsed =
|
|
2268
|
+
const parsed = parseYaml7(readFile(p));
|
|
2256
2269
|
return Array.isArray(parsed?.external) ? parsed.external : [];
|
|
2257
2270
|
} catch {
|
|
2258
2271
|
return [];
|
|
@@ -2283,7 +2296,7 @@ function sectionParagraph2(md, title) {
|
|
|
2283
2296
|
}
|
|
2284
2297
|
return "";
|
|
2285
2298
|
}
|
|
2286
|
-
function parseCapsule(id,
|
|
2299
|
+
function parseCapsule(id, path4, md) {
|
|
2287
2300
|
const { data } = matter4(md);
|
|
2288
2301
|
const updatedAt = data.updated_at ? String(data.updated_at) : void 0;
|
|
2289
2302
|
let ageDays = null;
|
|
@@ -2293,7 +2306,7 @@ function parseCapsule(id, path3, md) {
|
|
|
2293
2306
|
}
|
|
2294
2307
|
return {
|
|
2295
2308
|
id,
|
|
2296
|
-
path:
|
|
2309
|
+
path: path4,
|
|
2297
2310
|
system: data.system ? String(data.system) : id,
|
|
2298
2311
|
owner: data.owner ? String(data.owner) : void 0,
|
|
2299
2312
|
updatedAt,
|
|
@@ -2355,9 +2368,9 @@ function buildGraph(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
|
|
|
2355
2368
|
];
|
|
2356
2369
|
const presentLayers = [];
|
|
2357
2370
|
for (const layer2 of layerDocs) {
|
|
2358
|
-
const
|
|
2359
|
-
if (
|
|
2360
|
-
addNode({ id: layer2.id, type: layer2.type, label: layer2.label, path:
|
|
2371
|
+
const path4 = layer2.files.map((f3) => `${KNOWLEDGE3}/${f3}`).find((rel) => exists(join(dir, rel)));
|
|
2372
|
+
if (path4) {
|
|
2373
|
+
addNode({ id: layer2.id, type: layer2.type, label: layer2.label, path: path4 });
|
|
2361
2374
|
presentLayers.push(layer2.id);
|
|
2362
2375
|
}
|
|
2363
2376
|
}
|
|
@@ -3693,6 +3706,9 @@ function buildAdapterStatuses(dir) {
|
|
|
3693
3706
|
});
|
|
3694
3707
|
}
|
|
3695
3708
|
|
|
3709
|
+
// ../cli/src/core/next-step.ts
|
|
3710
|
+
import { parse as parseYaml8 } from "yaml";
|
|
3711
|
+
|
|
3696
3712
|
// ../cli/src/agents/groups.ts
|
|
3697
3713
|
var AGENT_GROUPS = {
|
|
3698
3714
|
business: ["business-agent.md"],
|
|
@@ -3834,6 +3850,7 @@ function buildDeliveryState(dir) {
|
|
|
3834
3850
|
refined_draft_ids: wis.filter((w3) => w3.lifecycle === "draft" && w3.rawFrontmatter.refined_by).map((w3) => w3.id),
|
|
3835
3851
|
ready_work_items: byState("ready"),
|
|
3836
3852
|
ready_work_item_ids: wis.filter((w3) => w3.lifecycle === "ready").map((w3) => w3.id),
|
|
3853
|
+
ready_multirepo_wi_ids: wis.filter((w3) => w3.lifecycle === "ready" && Array.isArray(w3.rawFrontmatter?.affected_modules) && w3.rawFrontmatter.affected_modules.length > 0).map((w3) => w3.id),
|
|
3837
3854
|
in_progress_work_items: byState("in-progress"),
|
|
3838
3855
|
blocked_work_items: byState("blocked"),
|
|
3839
3856
|
total_work_items: total,
|
|
@@ -3844,6 +3861,14 @@ function buildDeliveryState(dir) {
|
|
|
3844
3861
|
adapters_installed: adapters.length
|
|
3845
3862
|
};
|
|
3846
3863
|
}
|
|
3864
|
+
function hasRegisteredModules(modulesYmlPath) {
|
|
3865
|
+
try {
|
|
3866
|
+
const parsed = parseYaml8(readFile(modulesYmlPath));
|
|
3867
|
+
return Array.isArray(parsed?.modules) && parsed.modules.length > 0;
|
|
3868
|
+
} catch {
|
|
3869
|
+
return false;
|
|
3870
|
+
}
|
|
3871
|
+
}
|
|
3847
3872
|
function safeRead(p) {
|
|
3848
3873
|
try {
|
|
3849
3874
|
return readFile(p);
|
|
@@ -3894,11 +3919,12 @@ function resolveNextStep(dir, now = /* @__PURE__ */ new Date()) {
|
|
|
3894
3919
|
const state = config.project.state;
|
|
3895
3920
|
const q3 = (rel) => analyzeKnowledgeArtifact(dir, rel);
|
|
3896
3921
|
const moduleRepo = isModule(config);
|
|
3897
|
-
const
|
|
3922
|
+
const coreRepo = isCore(config);
|
|
3923
|
+
const MC = exists(join(dir, "knowledge/tech/module/module-context.md")) ? "knowledge/tech/module/module-context.md" : "knowledge/module/module-context.md";
|
|
3898
3924
|
if (moduleRepo) {
|
|
3899
3925
|
const qMC = q3(MC);
|
|
3900
3926
|
if (qMC === "missing") {
|
|
3901
|
-
return { id: "init-module-context", phase: "Setup", label: "Create `knowledge/module/module-context.md` for this module.", command: "kaddo init", reason: "Module context file is missing. Re-run `kaddo init` as a multirepo module." };
|
|
3927
|
+
return { id: "init-module-context", phase: "Setup", label: "Create `knowledge/tech/module/module-context.md` for this module.", command: "kaddo init", reason: "Module context file is missing. Re-run `kaddo init` as a multirepo module." };
|
|
3902
3928
|
}
|
|
3903
3929
|
if ((state === "pre-ai" || state === "legacy") && !exists(join(dir, ".kaddo", "scan.json"))) {
|
|
3904
3930
|
return { id: "scan", phase: "Discovery", label: "Run `kaddo scan` to capture deterministic signals from the existing code.", command: "kaddo scan", reason: "No scan baseline exists for this existing project yet." };
|
|
@@ -3947,9 +3973,15 @@ function resolveNextStep(dir, now = /* @__PURE__ */ new Date()) {
|
|
|
3947
3973
|
return { id: "module-ready", phase: "Active Delivery", label: "Module context is complete. Create and manage Work Items from the core repository.", reason: "This module's knowledge is ready. Work Items are created and tracked in the core." };
|
|
3948
3974
|
}
|
|
3949
3975
|
const qBusiness = q3(B2), qProduct = q3(P2), qCap = q3(CAP), qCurrentState = q3(CS), qCodebase = q3(CB);
|
|
3950
|
-
if (qBusiness === "missing" || qProduct === "missing") {
|
|
3976
|
+
if (!coreRepo && (qBusiness === "missing" || qProduct === "missing")) {
|
|
3951
3977
|
return { id: "bootstrap", phase: "Setup", label: "Run `kaddo bootstrap` to create the project knowledge baseline.", command: "kaddo bootstrap", reason: "The knowledge baseline is incomplete." };
|
|
3952
3978
|
}
|
|
3979
|
+
if (coreRepo) {
|
|
3980
|
+
const modulesYmlPath = join(dir, ".kaddo", "modules.yml");
|
|
3981
|
+
if (!exists(modulesYmlPath) || !hasRegisteredModules(modulesYmlPath)) {
|
|
3982
|
+
return { id: "modules-discover", phase: "Setup", label: "Run `kaddo modules discover` to find sibling module repositories.", command: "kaddo modules discover", reason: "No modules are mapped for this core repository." };
|
|
3983
|
+
}
|
|
3984
|
+
}
|
|
3953
3985
|
const ctx = buildCodexAdapterContext(dir);
|
|
3954
3986
|
if (!ctx.hasAgents) {
|
|
3955
3987
|
return { id: "add-agents", phase: "Setup", label: "Run `kaddo add agents` to install the Kaddo agent prompt packs.", command: "kaddo add agents", reason: "No Kaddo agents are installed yet." };
|
|
@@ -3966,10 +3998,10 @@ function resolveNextStep(dir, now = /* @__PURE__ */ new Date()) {
|
|
|
3966
3998
|
const discovery = state === "pre-ai" || state === "legacy";
|
|
3967
3999
|
const resolveAgent = (agent) => {
|
|
3968
4000
|
const file = agent.endsWith(".md") ? agent : `${agent}.md`;
|
|
3969
|
-
const
|
|
3970
|
-
const installed = isFile(join(dir,
|
|
4001
|
+
const path4 = agentInstallPath(file);
|
|
4002
|
+
const installed = isFile(join(dir, path4));
|
|
3971
4003
|
const group = agentGroupOf(file);
|
|
3972
|
-
return { agentPath:
|
|
4004
|
+
return { agentPath: path4, agentInstalled: installed, installCommand: installed ? void 0 : `kaddo add agents --group ${group}` };
|
|
3973
4005
|
};
|
|
3974
4006
|
const refine = (id, agent, target, quality, verb = "complete") => {
|
|
3975
4007
|
const ar = resolveAgent(agent);
|
|
@@ -4026,6 +4058,13 @@ function resolveNextStep(dir, now = /* @__PURE__ */ new Date()) {
|
|
|
4026
4058
|
reason: "The roadmap has no candidates yet, but ready Work Items exist."
|
|
4027
4059
|
});
|
|
4028
4060
|
}
|
|
4061
|
+
if (st.ready_multirepo_wi_ids.length > 0) {
|
|
4062
|
+
sec.push({
|
|
4063
|
+
id: "validate-work-item-modules",
|
|
4064
|
+
label: "Validate affected modules for multirepo Work Items before implementation.",
|
|
4065
|
+
reason: `${st.ready_multirepo_wi_ids.length} ready Work Item(s) reference affected_modules.`
|
|
4066
|
+
});
|
|
4067
|
+
}
|
|
4029
4068
|
if (adapters === 0) {
|
|
4030
4069
|
return {
|
|
4031
4070
|
id: "prepare-implementation",
|
|
@@ -7705,7 +7744,9 @@ function mapNextStepId(id) {
|
|
|
7705
7744
|
"implement-work-item": "prepare-implementation",
|
|
7706
7745
|
"refine-module-context": "refine-work-item",
|
|
7707
7746
|
"init-module-context": "enable-kaddo",
|
|
7708
|
-
"module-ready": "prepare-implementation"
|
|
7747
|
+
"module-ready": "prepare-implementation",
|
|
7748
|
+
"validate-work-item-modules": "prepare-implementation",
|
|
7749
|
+
"modules-discover": "enable-kaddo"
|
|
7709
7750
|
};
|
|
7710
7751
|
return MAP[id] ?? id;
|
|
7711
7752
|
}
|
|
@@ -8527,7 +8568,7 @@ function buildGuardHistory(dir) {
|
|
|
8527
8568
|
const d3 = hotspotDir(t.code_path);
|
|
8528
8569
|
hotspotMap.set(d3, (hotspotMap.get(d3) ?? 0) + 1);
|
|
8529
8570
|
}
|
|
8530
|
-
const hotspots = [...hotspotMap.entries()].map(([
|
|
8571
|
+
const hotspots = [...hotspotMap.entries()].map(([path4, warnings]) => ({ path: path4, warnings })).sort((a2, b2) => b2.warnings - a2.warnings);
|
|
8531
8572
|
return {
|
|
8532
8573
|
available: true,
|
|
8533
8574
|
total_runs: sorted.length,
|
|
@@ -8972,7 +9013,7 @@ function serializeImpactJson(r2) {
|
|
|
8972
9013
|
}
|
|
8973
9014
|
|
|
8974
9015
|
// ../cli/src/core/savings.ts
|
|
8975
|
-
import { parse as
|
|
9016
|
+
import { parse as parseYaml9 } from "yaml";
|
|
8976
9017
|
var DEFAULT_ASSUMPTIONS = {
|
|
8977
9018
|
currency: "USD",
|
|
8978
9019
|
hourly_cost: 40,
|
|
@@ -8990,7 +9031,7 @@ function loadAssumptions(dir) {
|
|
|
8990
9031
|
const p = join(dir, SAVINGS_PATH);
|
|
8991
9032
|
if (!exists(p)) return { assumptions: { ...DEFAULT_ASSUMPTIONS }, source: "default" };
|
|
8992
9033
|
try {
|
|
8993
|
-
const raw =
|
|
9034
|
+
const raw = parseYaml9(readFile(p));
|
|
8994
9035
|
const a2 = raw?.assumptions ?? {};
|
|
8995
9036
|
const t = raw?.team ?? {};
|
|
8996
9037
|
const num = (v, d3) => typeof v === "number" && Number.isFinite(v) ? v : d3;
|
|
@@ -9828,7 +9869,7 @@ function getWorkItem(root, id) {
|
|
|
9828
9869
|
const content = readText(root, item.path);
|
|
9829
9870
|
return ok({ ...item, content: content ?? "" });
|
|
9830
9871
|
}
|
|
9831
|
-
function markWorkItemReady(root, id,
|
|
9872
|
+
function markWorkItemReady(root, id, confirm2) {
|
|
9832
9873
|
const item = listWorkItems(root).find((w3) => w3.id === id);
|
|
9833
9874
|
if (!item) return fail(`Work Item "${id}" not found.`);
|
|
9834
9875
|
if (item.status === "ready") {
|
|
@@ -9838,7 +9879,7 @@ function markWorkItemReady(root, id, confirm) {
|
|
|
9838
9879
|
return fail(`Work Item ${item.id} is in state "${item.status}", not "draft". Only draft Work Items can be marked as ready.`);
|
|
9839
9880
|
}
|
|
9840
9881
|
const warnings = item.readiness?.warnings ?? [];
|
|
9841
|
-
if (!
|
|
9882
|
+
if (!confirm2) {
|
|
9842
9883
|
return ok({
|
|
9843
9884
|
status: "needs_confirmation",
|
|
9844
9885
|
workItem: {
|
|
@@ -9924,8 +9965,547 @@ function listGraphHints(root, filter = {}) {
|
|
|
9924
9965
|
});
|
|
9925
9966
|
}
|
|
9926
9967
|
|
|
9927
|
-
//
|
|
9968
|
+
// src/multirepo.ts
|
|
9969
|
+
import fs3 from "fs";
|
|
9970
|
+
import path3 from "path";
|
|
9928
9971
|
import matter11 from "gray-matter";
|
|
9972
|
+
import { parse as parseYaml10, stringify as stringifyYaml4 } from "yaml";
|
|
9973
|
+
var ok2 = (data) => ({ ok: true, data });
|
|
9974
|
+
var fail2 = (message) => ({ ok: false, message });
|
|
9975
|
+
function moduleRepoPath(m2) {
|
|
9976
|
+
return m2.path ?? m2.repoPath ?? "";
|
|
9977
|
+
}
|
|
9978
|
+
function loadModulesDescriptor(root) {
|
|
9979
|
+
return readYaml(root, ".kaddo/modules.yml") ?? { modules: [] };
|
|
9980
|
+
}
|
|
9981
|
+
function loadConfig2(root) {
|
|
9982
|
+
return readYaml(root, ".kaddo/config.yml");
|
|
9983
|
+
}
|
|
9984
|
+
function projectRole2(config) {
|
|
9985
|
+
return config.project?.role ?? config.multirepo?.role;
|
|
9986
|
+
}
|
|
9987
|
+
function isCore2(config) {
|
|
9988
|
+
return projectRole2(config) === "core";
|
|
9989
|
+
}
|
|
9990
|
+
function readModuleFile(root, repoPath, relFile) {
|
|
9991
|
+
const moduleDir = path3.resolve(root, repoPath);
|
|
9992
|
+
const abs = path3.resolve(moduleDir, relFile);
|
|
9993
|
+
if (!abs.startsWith(moduleDir)) return null;
|
|
9994
|
+
if (!fs3.existsSync(abs) || !fs3.statSync(abs).isFile()) return null;
|
|
9995
|
+
return fs3.readFileSync(abs, "utf-8");
|
|
9996
|
+
}
|
|
9997
|
+
function detectModuleStatus2(root, repoPath) {
|
|
9998
|
+
const moduleDir = path3.resolve(root, repoPath);
|
|
9999
|
+
if (!fs3.existsSync(moduleDir)) {
|
|
10000
|
+
return { status: "missing", configured: false, moduleContext: false, currentState: false, codebase: false, warning: `Directory \`${repoPath}\` does not exist.` };
|
|
10001
|
+
}
|
|
10002
|
+
const configPath = path3.join(moduleDir, ".kaddo", "config.yml");
|
|
10003
|
+
if (!fs3.existsSync(configPath)) {
|
|
10004
|
+
return { status: "not_configured", configured: false, moduleContext: false, currentState: false, codebase: false, warning: `Run \`kaddo init\` inside \`${repoPath}\` and select multirepo \u2192 module.` };
|
|
10005
|
+
}
|
|
10006
|
+
let parsed = null;
|
|
10007
|
+
try {
|
|
10008
|
+
parsed = parseYaml10(fs3.readFileSync(configPath, "utf-8"));
|
|
10009
|
+
} catch {
|
|
10010
|
+
}
|
|
10011
|
+
const role = parsed?.project?.role ?? parsed?.multirepo?.role;
|
|
10012
|
+
if (role !== "module") {
|
|
10013
|
+
return { status: "invalid", configured: true, role, moduleContext: false, currentState: false, codebase: false, warning: `Repository has Kaddo configured but project.role is not module.` };
|
|
10014
|
+
}
|
|
10015
|
+
return {
|
|
10016
|
+
status: "configured",
|
|
10017
|
+
configured: true,
|
|
10018
|
+
role: "module",
|
|
10019
|
+
moduleContext: fs3.existsSync(path3.join(moduleDir, "knowledge", "tech", "module", "module-context.md")) || fs3.existsSync(path3.join(moduleDir, "knowledge", "module", "module-context.md")),
|
|
10020
|
+
currentState: fs3.existsSync(path3.join(moduleDir, "knowledge", "tech", "current-state.md")),
|
|
10021
|
+
codebase: fs3.existsSync(path3.join(moduleDir, "knowledge", "tech", "codebase.md"))
|
|
10022
|
+
};
|
|
10023
|
+
}
|
|
10024
|
+
function resolveModuleContextPath(root, repoPath) {
|
|
10025
|
+
const moduleDir = path3.resolve(root, repoPath);
|
|
10026
|
+
if (fs3.existsSync(path3.join(moduleDir, "knowledge", "tech", "module", "module-context.md"))) {
|
|
10027
|
+
return "knowledge/tech/module/module-context.md";
|
|
10028
|
+
}
|
|
10029
|
+
return "knowledge/module/module-context.md";
|
|
10030
|
+
}
|
|
10031
|
+
function isPlaceholder(content) {
|
|
10032
|
+
const body = content.replace(/^---[\s\S]*?---\n?/, "");
|
|
10033
|
+
return /^_[^_]+_$/m.test(body) && !body.split("\n").some((l2) => l2.trim().length > 0 && !l2.startsWith("#") && !l2.startsWith("_") && !l2.startsWith("-") && !l2.startsWith(">"));
|
|
10034
|
+
}
|
|
10035
|
+
function firstParagraph3(md) {
|
|
10036
|
+
const body = md.replace(/^---[\s\S]*?---\n?/, "");
|
|
10037
|
+
for (const block of body.split(/\n\s*\n/)) {
|
|
10038
|
+
const t = block.trim();
|
|
10039
|
+
if (t && !t.startsWith("#") && !t.startsWith(">") && !t.startsWith("_")) return t.replace(/\s+/g, " ");
|
|
10040
|
+
}
|
|
10041
|
+
return "";
|
|
10042
|
+
}
|
|
10043
|
+
function slugify2(s) {
|
|
10044
|
+
return s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
|
|
10045
|
+
}
|
|
10046
|
+
function modulesListTool(root, args) {
|
|
10047
|
+
const config = loadConfig2(root);
|
|
10048
|
+
if (!config) return fail2("No Kaddo config found. Run `kaddo init` first.");
|
|
10049
|
+
if (!isCore2(config)) return fail2("kaddo_modules_list can only be called from a core repository (project.role: core).");
|
|
10050
|
+
const descriptor = loadModulesDescriptor(root);
|
|
10051
|
+
const modules = (descriptor.modules ?? []).map((m2) => {
|
|
10052
|
+
const rp = moduleRepoPath(m2);
|
|
10053
|
+
const st = detectModuleStatus2(root, rp);
|
|
10054
|
+
const entry = {
|
|
10055
|
+
id: m2.id,
|
|
10056
|
+
repo: rp,
|
|
10057
|
+
status: st.status,
|
|
10058
|
+
kaddo: {
|
|
10059
|
+
configured: st.configured,
|
|
10060
|
+
role: st.role,
|
|
10061
|
+
moduleContext: st.moduleContext,
|
|
10062
|
+
currentState: st.currentState,
|
|
10063
|
+
codebase: st.codebase
|
|
10064
|
+
}
|
|
10065
|
+
};
|
|
10066
|
+
if (args.includeWarnings && st.warning) entry.warning = st.warning;
|
|
10067
|
+
return entry;
|
|
10068
|
+
});
|
|
10069
|
+
return ok2({
|
|
10070
|
+
project: { name: config.project?.name ?? "unknown", structure: config.project?.structure ?? "unknown", role: "core" },
|
|
10071
|
+
modules
|
|
10072
|
+
});
|
|
10073
|
+
}
|
|
10074
|
+
function getModuleContextTool(root, args) {
|
|
10075
|
+
const config = loadConfig2(root);
|
|
10076
|
+
if (!config) return fail2("No Kaddo config found.");
|
|
10077
|
+
const descriptor = loadModulesDescriptor(root);
|
|
10078
|
+
const mod = (descriptor.modules ?? []).find((m2) => m2.id === args.module);
|
|
10079
|
+
if (!mod) return fail2(`Module "${args.module}" not found in .kaddo/modules.yml.`);
|
|
10080
|
+
const rp = moduleRepoPath(mod);
|
|
10081
|
+
const st = detectModuleStatus2(root, rp);
|
|
10082
|
+
const warnings = [];
|
|
10083
|
+
if (st.status !== "configured") {
|
|
10084
|
+
if (st.warning) warnings.push(st.warning);
|
|
10085
|
+
return ok2({
|
|
10086
|
+
module: { id: mod.id, status: st.status },
|
|
10087
|
+
context: null,
|
|
10088
|
+
warnings,
|
|
10089
|
+
recommendedNextStep: { label: "Initialize module repository with Kaddo.", command: "kaddo init" }
|
|
10090
|
+
});
|
|
10091
|
+
}
|
|
10092
|
+
const mcRelPath = resolveModuleContextPath(root, rp);
|
|
10093
|
+
const mcContent = readModuleFile(root, rp, mcRelPath);
|
|
10094
|
+
const context = {
|
|
10095
|
+
moduleContextPath: `${rp}/${mcRelPath}`,
|
|
10096
|
+
moduleContext: mcContent ?? null
|
|
10097
|
+
};
|
|
10098
|
+
if (mcContent && isPlaceholder(mcContent)) {
|
|
10099
|
+
warnings.push("module-context.md still contains placeholder sections.");
|
|
10100
|
+
}
|
|
10101
|
+
if (args.includeTech) {
|
|
10102
|
+
const cs = readModuleFile(root, rp, "knowledge/tech/current-state.md");
|
|
10103
|
+
const cb = readModuleFile(root, rp, "knowledge/tech/codebase.md");
|
|
10104
|
+
context.currentStatePath = `${rp}/knowledge/tech/current-state.md`;
|
|
10105
|
+
context.currentStateSummary = cs ? firstParagraph3(cs) : null;
|
|
10106
|
+
context.codebasePath = `${rp}/knowledge/tech/codebase.md`;
|
|
10107
|
+
context.codebaseSummary = cb ? firstParagraph3(cb) : null;
|
|
10108
|
+
if (!cs) warnings.push("current-state.md not found in module repository.");
|
|
10109
|
+
if (!cb) warnings.push("codebase.md not found in module repository.");
|
|
10110
|
+
}
|
|
10111
|
+
return ok2({
|
|
10112
|
+
module: { id: mod.id, repo: rp, status: "configured" },
|
|
10113
|
+
context,
|
|
10114
|
+
warnings: args.includeWarnings ? warnings : []
|
|
10115
|
+
});
|
|
10116
|
+
}
|
|
10117
|
+
function validateWorkItemModulesTool(root, args) {
|
|
10118
|
+
const items = listWorkItems(root);
|
|
10119
|
+
const wi = items.find((w3) => w3.id === args.workItemId);
|
|
10120
|
+
if (!wi) return fail2(`Work Item "${args.workItemId}" not found.`);
|
|
10121
|
+
const raw = readText(root, wi.path);
|
|
10122
|
+
if (!raw) return fail2(`Could not read Work Item file.`);
|
|
10123
|
+
const parsed = matter11(raw);
|
|
10124
|
+
const data = parsed.data;
|
|
10125
|
+
const affectedModules = Array.isArray(data.affected_modules) ? data.affected_modules.map(String) : [];
|
|
10126
|
+
const codeEntries = Array.isArray(data.code) ? data.code.filter((c) => typeof c === "object" && c !== null).map((c) => ({ repo: typeof c.repo === "string" ? c.repo : void 0, paths: Array.isArray(c.paths) ? c.paths.map(String) : [] })) : [];
|
|
10127
|
+
const descriptor = loadModulesDescriptor(root);
|
|
10128
|
+
const mapped = new Set((descriptor.modules ?? []).map((m2) => m2.id));
|
|
10129
|
+
const warnings = [];
|
|
10130
|
+
const errors = [];
|
|
10131
|
+
if (affectedModules.length === 0) {
|
|
10132
|
+
warnings.push("Work Item has no affected_modules declared.");
|
|
10133
|
+
}
|
|
10134
|
+
const moduleValidations = affectedModules.map((modId) => {
|
|
10135
|
+
if (!mapped.has(modId)) {
|
|
10136
|
+
errors.push(`Module "${modId}" is not in .kaddo/modules.yml.`);
|
|
10137
|
+
return { id: modId, status: "not_mapped", hasModuleContext: false, hasCurrentState: false, hasCodebase: false, hasOwnership: false };
|
|
10138
|
+
}
|
|
10139
|
+
const mod = (descriptor.modules ?? []).find((m2) => m2.id === modId);
|
|
10140
|
+
const rp = moduleRepoPath(mod);
|
|
10141
|
+
const st = detectModuleStatus2(root, rp);
|
|
10142
|
+
if (st.status !== "configured") {
|
|
10143
|
+
warnings.push(`Module "${modId}" is ${st.status}.`);
|
|
10144
|
+
}
|
|
10145
|
+
const mcRelPath = resolveModuleContextPath(root, rp);
|
|
10146
|
+
const mcContent = st.moduleContext ? readModuleFile(root, rp, mcRelPath) : null;
|
|
10147
|
+
if (mcContent && isPlaceholder(mcContent)) {
|
|
10148
|
+
warnings.push(`Module "${modId}" has placeholder module-context.`);
|
|
10149
|
+
}
|
|
10150
|
+
const hasOwnership = codeEntries.some((c) => c.repo && (c.repo === rp || c.repo.endsWith(`/${modId}`) || c.repo.includes(modId)));
|
|
10151
|
+
return { id: modId, status: st.status, hasModuleContext: st.moduleContext, hasCurrentState: st.currentState, hasCodebase: st.codebase, hasOwnership };
|
|
10152
|
+
});
|
|
10153
|
+
for (const ce of codeEntries) {
|
|
10154
|
+
if (ce.repo && !affectedModules.some((m2) => ce.repo.includes(m2))) {
|
|
10155
|
+
warnings.push(`code.repo "${ce.repo}" is not listed in affected_modules.`);
|
|
10156
|
+
}
|
|
10157
|
+
}
|
|
10158
|
+
for (const mv of moduleValidations) {
|
|
10159
|
+
if (affectedModules.includes(mv.id) && !mv.hasOwnership) {
|
|
10160
|
+
warnings.push(`Module "${mv.id}" is in affected_modules but has no paths in code.`);
|
|
10161
|
+
}
|
|
10162
|
+
}
|
|
10163
|
+
return ok2({
|
|
10164
|
+
workItem: { id: wi.id, title: wi.title, status: wi.status, scope: affectedModules.length > 0 ? "multirepo" : "single", affectedModules },
|
|
10165
|
+
validation: { valid: errors.length === 0, modules: moduleValidations, warnings, errors }
|
|
10166
|
+
});
|
|
10167
|
+
}
|
|
10168
|
+
function getWorkItemContextTool(root, args) {
|
|
10169
|
+
const config = loadConfig2(root);
|
|
10170
|
+
if (!config) return fail2("No Kaddo config found.");
|
|
10171
|
+
const items = listWorkItems(root);
|
|
10172
|
+
const wi = items.find((w3) => w3.id === args.workItemId);
|
|
10173
|
+
if (!wi) return fail2(`Work Item "${args.workItemId}" not found.`);
|
|
10174
|
+
const raw = readText(root, wi.path);
|
|
10175
|
+
if (!raw) return fail2(`Could not read Work Item file.`);
|
|
10176
|
+
const parsed = matter11(raw);
|
|
10177
|
+
const data = parsed.data;
|
|
10178
|
+
const affectedModules = Array.isArray(data.affected_modules) ? data.affected_modules.map(String) : [];
|
|
10179
|
+
const codeEntries = Array.isArray(data.code) ? data.code.filter((c) => typeof c === "object" && c !== null).map((c) => ({ repo: typeof c.repo === "string" ? c.repo : void 0, paths: Array.isArray(c.paths) ? c.paths.map(String) : [] })) : [];
|
|
10180
|
+
const descriptor = loadModulesDescriptor(root);
|
|
10181
|
+
const warnings = [];
|
|
10182
|
+
const modules = affectedModules.map((modId) => {
|
|
10183
|
+
const mod = (descriptor.modules ?? []).find((m2) => m2.id === modId);
|
|
10184
|
+
if (!mod) {
|
|
10185
|
+
warnings.push(`Module "${modId}" not in modules.yml.`);
|
|
10186
|
+
return { id: modId, repo: null, moduleContext: null, currentStateSummary: null, codebaseSummary: null };
|
|
10187
|
+
}
|
|
10188
|
+
const rp = moduleRepoPath(mod);
|
|
10189
|
+
const st = detectModuleStatus2(root, rp);
|
|
10190
|
+
if (st.status !== "configured") warnings.push(`Module "${modId}" is ${st.status}.`);
|
|
10191
|
+
const mcRelPath = resolveModuleContextPath(root, rp);
|
|
10192
|
+
const mc = args.includeModuleContexts !== false ? readModuleFile(root, rp, mcRelPath) : null;
|
|
10193
|
+
const cs = readModuleFile(root, rp, "knowledge/tech/current-state.md");
|
|
10194
|
+
const cb = readModuleFile(root, rp, "knowledge/tech/codebase.md");
|
|
10195
|
+
return { id: modId, repo: rp, moduleContext: mc, currentStateSummary: cs ? firstParagraph3(cs) : null, codebaseSummary: cb ? firstParagraph3(cb) : null };
|
|
10196
|
+
});
|
|
10197
|
+
const ownership = { crossRepo: codeEntries.length > 1, items: codeEntries.filter((c) => c.repo).map((c) => ({ repo: c.repo, paths: c.paths ?? [] })) };
|
|
10198
|
+
const result = {
|
|
10199
|
+
project: { name: config.project?.name ?? "unknown", role: projectRole2(config) ?? "unknown", structure: config.project?.structure ?? "unknown" },
|
|
10200
|
+
workItem: { id: wi.id, title: wi.title, status: wi.status, affectedModules, acceptanceCriteria: extractSection(parsed.content, "Acceptance criteria"), outOfScope: extractSection(parsed.content, "Out of scope") },
|
|
10201
|
+
modules,
|
|
10202
|
+
ownership,
|
|
10203
|
+
warnings
|
|
10204
|
+
};
|
|
10205
|
+
if (args.includeBranchStrategy !== false && affectedModules.length > 0) {
|
|
10206
|
+
result.branchStrategy = buildBranchStrategy(wi, affectedModules, codeEntries, descriptor);
|
|
10207
|
+
}
|
|
10208
|
+
if (args.includeGuardExpectations) {
|
|
10209
|
+
result.guardExpectations = [
|
|
10210
|
+
"Run guard from core before closing the Work Item.",
|
|
10211
|
+
"Validate ownership across all affected repos."
|
|
10212
|
+
];
|
|
10213
|
+
}
|
|
10214
|
+
return ok2(result);
|
|
10215
|
+
}
|
|
10216
|
+
function extractSection(content, heading) {
|
|
10217
|
+
const re = new RegExp(`^##\\s+${heading}\\s*$`, "im");
|
|
10218
|
+
const lines = content.split(/\r?\n/);
|
|
10219
|
+
const start = lines.findIndex((l2) => re.test(l2));
|
|
10220
|
+
if (start < 0) return [];
|
|
10221
|
+
const out = [];
|
|
10222
|
+
for (let i = start + 1; i < lines.length; i++) {
|
|
10223
|
+
if (/^##\s/.test(lines[i])) break;
|
|
10224
|
+
const m2 = lines[i].match(/^\s*-\s*\[[ x]?\]\s*(.+?)\s*$/);
|
|
10225
|
+
if (m2) out.push(m2[1]);
|
|
10226
|
+
}
|
|
10227
|
+
return out;
|
|
10228
|
+
}
|
|
10229
|
+
function buildBranchStrategy(wi, affectedModules, codeEntries, descriptor) {
|
|
10230
|
+
const slug4 = slugify2(wi.title);
|
|
10231
|
+
const wiLower = wi.id.toLowerCase();
|
|
10232
|
+
const coreBranch = `feature/${wiLower}-${slug4}`;
|
|
10233
|
+
const moduleBranches = {};
|
|
10234
|
+
const commitMessages = {};
|
|
10235
|
+
for (const modId of affectedModules) {
|
|
10236
|
+
const mod = (descriptor.modules ?? []).find((m2) => m2.id === modId);
|
|
10237
|
+
const repo = mod ? moduleRepoPath(mod) || modId : modId;
|
|
10238
|
+
moduleBranches[repo] = `feature/${wiLower}-${modId}-${slug4.slice(0, 20)}`;
|
|
10239
|
+
commitMessages[repo] = `fix(${wiLower}): update ${modId}`;
|
|
10240
|
+
}
|
|
10241
|
+
commitMessages["core"] = `docs(${wiLower}): capture cross-repo implementation summary`;
|
|
10242
|
+
return {
|
|
10243
|
+
type: affectedModules.length > 0 ? "multi-repo" : "single-repo",
|
|
10244
|
+
branches: { core: coreBranch, modules: moduleBranches },
|
|
10245
|
+
commitMessages,
|
|
10246
|
+
checklist: [
|
|
10247
|
+
"Create branches manually in each repo.",
|
|
10248
|
+
"Implement changes in affected module repos.",
|
|
10249
|
+
"Run validation per repo.",
|
|
10250
|
+
"Run Kaddo guard from core.",
|
|
10251
|
+
"Review diffs manually.",
|
|
10252
|
+
"Commit only with explicit human confirmation."
|
|
10253
|
+
]
|
|
10254
|
+
};
|
|
10255
|
+
}
|
|
10256
|
+
function suggestBranchStrategyTool(root, args) {
|
|
10257
|
+
const items = listWorkItems(root);
|
|
10258
|
+
const wi = items.find((w3) => w3.id === args.workItemId);
|
|
10259
|
+
if (!wi) return fail2(`Work Item "${args.workItemId}" not found.`);
|
|
10260
|
+
const raw = readText(root, wi.path);
|
|
10261
|
+
if (!raw) return fail2(`Could not read Work Item file.`);
|
|
10262
|
+
const parsed = matter11(raw);
|
|
10263
|
+
const data = parsed.data;
|
|
10264
|
+
const affectedModules = Array.isArray(data.affected_modules) ? data.affected_modules.map(String) : [];
|
|
10265
|
+
const codeEntries = Array.isArray(data.code) ? data.code.filter((c) => typeof c === "object" && c !== null).map((c) => ({ repo: typeof c.repo === "string" ? c.repo : void 0, paths: Array.isArray(c.paths) ? c.paths.map(String) : [] })) : [];
|
|
10266
|
+
const descriptor = loadModulesDescriptor(root);
|
|
10267
|
+
const strategy = buildBranchStrategy(wi, affectedModules, codeEntries, descriptor);
|
|
10268
|
+
return ok2({
|
|
10269
|
+
workItem: { id: wi.id, title: wi.title },
|
|
10270
|
+
strategy,
|
|
10271
|
+
safety: { gitCommandsExecuted: false, message: "Kaddo only suggests branch and commit strategy. It does not execute git commands." }
|
|
10272
|
+
});
|
|
10273
|
+
}
|
|
10274
|
+
function exportCapsuleTool(root, args) {
|
|
10275
|
+
const config = loadConfig2(root);
|
|
10276
|
+
if (!config) return fail2("No Kaddo config found.");
|
|
10277
|
+
const role = projectRole2(config);
|
|
10278
|
+
const warnings = [];
|
|
10279
|
+
let capsuleType;
|
|
10280
|
+
let capsuleName;
|
|
10281
|
+
let capsuleData;
|
|
10282
|
+
if (args.module) {
|
|
10283
|
+
if (role === "core") {
|
|
10284
|
+
const descriptor = loadModulesDescriptor(root);
|
|
10285
|
+
const mod = (descriptor.modules ?? []).find((m2) => m2.id === args.module);
|
|
10286
|
+
if (!mod) return fail2(`Module "${args.module}" not found in .kaddo/modules.yml.`);
|
|
10287
|
+
const rp = moduleRepoPath(mod);
|
|
10288
|
+
const mcRelPath = resolveModuleContextPath(root, rp);
|
|
10289
|
+
const mc = readModuleFile(root, rp, mcRelPath);
|
|
10290
|
+
if (!mc) warnings.push("module-context.md not found for this module.");
|
|
10291
|
+
if (mc && isPlaceholder(mc)) warnings.push("module-context.md contains placeholder sections.");
|
|
10292
|
+
capsuleType = "module";
|
|
10293
|
+
capsuleName = `${slugify2(config.project?.name ?? "project")}-${args.module}`;
|
|
10294
|
+
capsuleData = {
|
|
10295
|
+
type: "knowledge-capsule",
|
|
10296
|
+
system: `${config.project?.name ?? "unknown"}/${args.module}`,
|
|
10297
|
+
version: 1,
|
|
10298
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
|
|
10299
|
+
owner: mod.owner ?? "unknown",
|
|
10300
|
+
source_project: config.project?.name ?? "unknown",
|
|
10301
|
+
purpose: mc ? firstParagraph3(mc) : "",
|
|
10302
|
+
responsibilities: [],
|
|
10303
|
+
capabilities: mod.capabilities ?? [],
|
|
10304
|
+
contracts: [],
|
|
10305
|
+
dependencies: [],
|
|
10306
|
+
knownRisks: [],
|
|
10307
|
+
owners: mod.owner ? [mod.owner] : []
|
|
10308
|
+
};
|
|
10309
|
+
} else if (role === "module") {
|
|
10310
|
+
const mc = readText(root, "knowledge/tech/module/module-context.md") ?? readText(root, "knowledge/module/module-context.md");
|
|
10311
|
+
if (!mc) warnings.push("module-context.md not found.");
|
|
10312
|
+
if (mc && isPlaceholder(mc)) warnings.push("module-context.md contains placeholder sections.");
|
|
10313
|
+
const modId = config.module?.id ?? args.module;
|
|
10314
|
+
capsuleType = "module";
|
|
10315
|
+
capsuleName = slugify2(modId);
|
|
10316
|
+
capsuleData = {
|
|
10317
|
+
type: "knowledge-capsule",
|
|
10318
|
+
system: `${config.multirepo?.parent_system ?? "unknown"}/${modId}`,
|
|
10319
|
+
version: 1,
|
|
10320
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
|
|
10321
|
+
purpose: mc ? firstParagraph3(mc) : "",
|
|
10322
|
+
responsibilities: [],
|
|
10323
|
+
capabilities: [],
|
|
10324
|
+
contracts: [],
|
|
10325
|
+
dependencies: [],
|
|
10326
|
+
knownRisks: [],
|
|
10327
|
+
owners: []
|
|
10328
|
+
};
|
|
10329
|
+
} else {
|
|
10330
|
+
return fail2("Cannot export module capsule: project role is neither core nor module.");
|
|
10331
|
+
}
|
|
10332
|
+
} else if (args.scope === "system") {
|
|
10333
|
+
if (role !== "core") return fail2("--scope system can only be used from a core repository.");
|
|
10334
|
+
const descriptor = loadModulesDescriptor(root);
|
|
10335
|
+
const responsibilities = [];
|
|
10336
|
+
for (const m2 of descriptor.modules ?? []) {
|
|
10337
|
+
const rp = moduleRepoPath(m2);
|
|
10338
|
+
const mcRelPath = resolveModuleContextPath(root, rp);
|
|
10339
|
+
const mc = readModuleFile(root, rp, mcRelPath);
|
|
10340
|
+
responsibilities.push(mc ? `[${m2.id}] ${firstParagraph3(mc) || m2.name || m2.id}` : `[${m2.id}] ${m2.name || m2.id} (no module-context)`);
|
|
10341
|
+
}
|
|
10342
|
+
capsuleType = "system";
|
|
10343
|
+
capsuleName = `${slugify2(config.project?.name ?? "project")}-system`;
|
|
10344
|
+
capsuleData = {
|
|
10345
|
+
type: "knowledge-capsule",
|
|
10346
|
+
system: config.project?.name ?? "unknown",
|
|
10347
|
+
version: 1,
|
|
10348
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
|
|
10349
|
+
purpose: "",
|
|
10350
|
+
responsibilities,
|
|
10351
|
+
capabilities: [],
|
|
10352
|
+
contracts: [],
|
|
10353
|
+
dependencies: [],
|
|
10354
|
+
knownRisks: [],
|
|
10355
|
+
owners: []
|
|
10356
|
+
};
|
|
10357
|
+
} else {
|
|
10358
|
+
if (role === "module") {
|
|
10359
|
+
const mc = readText(root, "knowledge/tech/module/module-context.md") ?? readText(root, "knowledge/module/module-context.md");
|
|
10360
|
+
if (!mc) warnings.push("module-context.md not found.");
|
|
10361
|
+
if (mc && isPlaceholder(mc)) warnings.push("module-context.md contains placeholder sections.");
|
|
10362
|
+
const modId = config.module?.id ?? "module";
|
|
10363
|
+
capsuleType = "module";
|
|
10364
|
+
capsuleName = slugify2(modId);
|
|
10365
|
+
capsuleData = {
|
|
10366
|
+
type: "knowledge-capsule",
|
|
10367
|
+
system: `${config.multirepo?.parent_system ?? "unknown"}/${modId}`,
|
|
10368
|
+
version: 1,
|
|
10369
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
|
|
10370
|
+
purpose: mc ? firstParagraph3(mc) : "",
|
|
10371
|
+
responsibilities: [],
|
|
10372
|
+
capabilities: [],
|
|
10373
|
+
contracts: [],
|
|
10374
|
+
dependencies: [],
|
|
10375
|
+
knownRisks: [],
|
|
10376
|
+
owners: []
|
|
10377
|
+
};
|
|
10378
|
+
} else {
|
|
10379
|
+
capsuleType = "project";
|
|
10380
|
+
capsuleName = slugify2(config.project?.name ?? "project");
|
|
10381
|
+
capsuleData = {
|
|
10382
|
+
type: "knowledge-capsule",
|
|
10383
|
+
system: config.project?.name ?? "unknown",
|
|
10384
|
+
version: 1,
|
|
10385
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
|
|
10386
|
+
purpose: "",
|
|
10387
|
+
responsibilities: [],
|
|
10388
|
+
capabilities: [],
|
|
10389
|
+
contracts: [],
|
|
10390
|
+
dependencies: [],
|
|
10391
|
+
knownRisks: [],
|
|
10392
|
+
owners: []
|
|
10393
|
+
};
|
|
10394
|
+
}
|
|
10395
|
+
}
|
|
10396
|
+
const jsonPath = `.kaddo/exports/${capsuleName}.capsule.json`;
|
|
10397
|
+
const mdPath = `.kaddo/exports/${capsuleName}.capsule.md`;
|
|
10398
|
+
const jsonContent = JSON.stringify(capsuleData, null, 2) + "\n";
|
|
10399
|
+
const mdContent = renderCapsuleMd(capsuleData);
|
|
10400
|
+
writeDerived(root, jsonPath, jsonContent);
|
|
10401
|
+
writeDerived(root, mdPath, mdContent);
|
|
10402
|
+
return ok2({ exported: true, type: capsuleType, module: args.module ?? void 0, paths: { json: jsonPath, md: mdPath }, warnings });
|
|
10403
|
+
}
|
|
10404
|
+
function modulesDiscoverTool(root, args) {
|
|
10405
|
+
const config = loadConfig2(root);
|
|
10406
|
+
if (!config) return fail2("No Kaddo config found.");
|
|
10407
|
+
if (!isCore2(config)) return fail2("Module discovery is only available from a core repository.");
|
|
10408
|
+
const sysName = config.system?.name ?? config.project?.name ?? "";
|
|
10409
|
+
const workspaceRoots2 = config.multirepo?.workspace_roots ?? [".."];
|
|
10410
|
+
const IGNORED = /* @__PURE__ */ new Set([".git", "node_modules", "dist", "build", "coverage", "vendor", ".next", "out", "tmp", "temp", ".kaddo"]);
|
|
10411
|
+
const coreAbs = path3.resolve(root);
|
|
10412
|
+
const discovered = [];
|
|
10413
|
+
for (const wr of workspaceRoots2) {
|
|
10414
|
+
const rootAbs = path3.resolve(root, wr);
|
|
10415
|
+
if (!fs3.existsSync(rootAbs) || !fs3.statSync(rootAbs).isDirectory()) continue;
|
|
10416
|
+
let entries;
|
|
10417
|
+
try {
|
|
10418
|
+
entries = fs3.readdirSync(rootAbs);
|
|
10419
|
+
} catch {
|
|
10420
|
+
continue;
|
|
10421
|
+
}
|
|
10422
|
+
for (const entry of entries) {
|
|
10423
|
+
if (IGNORED.has(entry)) continue;
|
|
10424
|
+
const candidateAbs = path3.resolve(rootAbs, entry);
|
|
10425
|
+
if (!fs3.statSync(candidateAbs).isDirectory()) continue;
|
|
10426
|
+
if (candidateAbs === coreAbs) continue;
|
|
10427
|
+
const relPath = path3.relative(root, candidateAbs).replace(/\\/g, "/");
|
|
10428
|
+
const cfgPath = path3.join(candidateAbs, ".kaddo", "config.yml");
|
|
10429
|
+
if (!fs3.existsSync(cfgPath)) {
|
|
10430
|
+
discovered.push({ id: entry, path: relPath, status: "not_configured", eligibleForMapping: false });
|
|
10431
|
+
continue;
|
|
10432
|
+
}
|
|
10433
|
+
let parsed = null;
|
|
10434
|
+
try {
|
|
10435
|
+
parsed = parseYaml10(fs3.readFileSync(cfgPath, "utf-8"));
|
|
10436
|
+
} catch {
|
|
10437
|
+
}
|
|
10438
|
+
const role = parsed?.project?.role ?? parsed?.multirepo?.role;
|
|
10439
|
+
if (role !== "module") {
|
|
10440
|
+
discovered.push({ id: entry, path: relPath, status: "invalid", eligibleForMapping: false, warning: "Not configured as module." });
|
|
10441
|
+
continue;
|
|
10442
|
+
}
|
|
10443
|
+
const modParent = parsed?.multirepo?.parent_system ?? "";
|
|
10444
|
+
if (modParent && modParent !== sysName) {
|
|
10445
|
+
discovered.push({ id: parsed?.module?.id ?? entry, path: relPath, status: "foreign_system", eligibleForMapping: false, parentSystem: modParent });
|
|
10446
|
+
continue;
|
|
10447
|
+
}
|
|
10448
|
+
discovered.push({ id: parsed?.module?.id ?? entry, path: relPath, status: "configured", eligibleForMapping: true });
|
|
10449
|
+
}
|
|
10450
|
+
}
|
|
10451
|
+
if (args.apply) {
|
|
10452
|
+
if (!args.confirm) {
|
|
10453
|
+
return ok2({ discovered, applied: false, message: "Set confirm: true to apply discovered modules." });
|
|
10454
|
+
}
|
|
10455
|
+
const descriptor = loadModulesDescriptor(root);
|
|
10456
|
+
for (const d3 of discovered.filter((dd) => dd.eligibleForMapping)) {
|
|
10457
|
+
if (!(descriptor.modules ?? []).some((m2) => m2.id === d3.id)) {
|
|
10458
|
+
if (!descriptor.modules) descriptor.modules = [];
|
|
10459
|
+
descriptor.modules.push({ id: d3.id, name: d3.id, path: d3.path, parent_system: sysName, status: "configured" });
|
|
10460
|
+
}
|
|
10461
|
+
}
|
|
10462
|
+
const ymlContent = stringifyYaml4({ version: descriptor.version ?? 1, system: sysName, workspace_roots: workspaceRoots2, modules: descriptor.modules ?? [] });
|
|
10463
|
+
writeDerived(root, ".kaddo/modules.yml", ymlContent);
|
|
10464
|
+
return ok2({ discovered, applied: true });
|
|
10465
|
+
}
|
|
10466
|
+
return ok2({ discovered, applied: false });
|
|
10467
|
+
}
|
|
10468
|
+
function renderCapsuleMd(data) {
|
|
10469
|
+
const fm = ["---", `type: knowledge-capsule`, `system: ${data.system}`, `version: ${data.version}`, `updated_at: ${data.updated_at}`, "---", ""].join("\n");
|
|
10470
|
+
const list2 = (items) => items.length === 0 ? "_To be completed._" : items.map((i) => `- ${i}`).join("\n");
|
|
10471
|
+
return [
|
|
10472
|
+
fm,
|
|
10473
|
+
`# ${data.system} \u2014 Knowledge Capsule
|
|
10474
|
+
`,
|
|
10475
|
+
`## Purpose
|
|
10476
|
+
|
|
10477
|
+
${data.purpose || "_To be completed._"}
|
|
10478
|
+
`,
|
|
10479
|
+
`## Responsibilities
|
|
10480
|
+
|
|
10481
|
+
${list2(data.responsibilities)}
|
|
10482
|
+
`,
|
|
10483
|
+
`## Capabilities
|
|
10484
|
+
|
|
10485
|
+
${list2(data.capabilities)}
|
|
10486
|
+
`,
|
|
10487
|
+
`## Contracts
|
|
10488
|
+
|
|
10489
|
+
${list2(data.contracts)}
|
|
10490
|
+
`,
|
|
10491
|
+
`## Dependencies
|
|
10492
|
+
|
|
10493
|
+
${list2(data.dependencies)}
|
|
10494
|
+
`,
|
|
10495
|
+
`## Known Risks
|
|
10496
|
+
|
|
10497
|
+
${list2(data.knownRisks)}
|
|
10498
|
+
`,
|
|
10499
|
+
`## Owners
|
|
10500
|
+
|
|
10501
|
+
${list2(data.owners)}
|
|
10502
|
+
`,
|
|
10503
|
+
"> Security: this capsule must never contain secrets, tokens, credentials, PII or source code.\n"
|
|
10504
|
+
].join("\n");
|
|
10505
|
+
}
|
|
10506
|
+
|
|
10507
|
+
// ../cli/src/core/context-pack.ts
|
|
10508
|
+
import matter12 from "gray-matter";
|
|
9929
10509
|
var CONTEXT_PACK_VERSION = "1";
|
|
9930
10510
|
var ARCH_DIR3 = "knowledge";
|
|
9931
10511
|
function readScanJson(dir) {
|
|
@@ -9937,8 +10517,8 @@ function readScanJson(dir) {
|
|
|
9937
10517
|
return null;
|
|
9938
10518
|
}
|
|
9939
10519
|
}
|
|
9940
|
-
function
|
|
9941
|
-
const body =
|
|
10520
|
+
function firstParagraph4(markdown) {
|
|
10521
|
+
const body = matter12(markdown).content.trim();
|
|
9942
10522
|
const para = body.split("\n\n").map((p) => p.trim()).find((p) => p && !p.startsWith("#"));
|
|
9943
10523
|
return para ?? "";
|
|
9944
10524
|
}
|
|
@@ -9946,7 +10526,7 @@ function readMarkdownSummary(dir, file) {
|
|
|
9946
10526
|
const p = join(dir, ARCH_DIR3, file);
|
|
9947
10527
|
if (!exists(p)) return null;
|
|
9948
10528
|
try {
|
|
9949
|
-
return
|
|
10529
|
+
return firstParagraph4(readFile(p));
|
|
9950
10530
|
} catch {
|
|
9951
10531
|
return null;
|
|
9952
10532
|
}
|
|
@@ -11112,6 +11692,84 @@ function createServer(root) {
|
|
|
11112
11692
|
{ title: "Get skill", description: "Get an installed reusable skill by id.", inputSchema: { id: z3.string() } },
|
|
11113
11693
|
async (args) => toolText(guarded(root, () => getSkillTool(root, args.id)))
|
|
11114
11694
|
);
|
|
11695
|
+
server.registerTool(
|
|
11696
|
+
"kaddo_modules_list",
|
|
11697
|
+
{
|
|
11698
|
+
title: "List multirepo modules",
|
|
11699
|
+
description: "List mapped modules and their Kaddo configuration status. Only works from a core repository.",
|
|
11700
|
+
inputSchema: { includeWarnings: z3.boolean().optional() }
|
|
11701
|
+
},
|
|
11702
|
+
async (args) => toolText(guarded(root, () => modulesListTool(root, args)))
|
|
11703
|
+
);
|
|
11704
|
+
server.registerTool(
|
|
11705
|
+
"kaddo_get_module_context",
|
|
11706
|
+
{
|
|
11707
|
+
title: "Get module context",
|
|
11708
|
+
description: "Get the local knowledge context (module-context.md, tech files) for a mapped module.",
|
|
11709
|
+
inputSchema: {
|
|
11710
|
+
module: z3.string(),
|
|
11711
|
+
includeTech: z3.boolean().optional(),
|
|
11712
|
+
includeWarnings: z3.boolean().optional()
|
|
11713
|
+
}
|
|
11714
|
+
},
|
|
11715
|
+
async (args) => toolText(guarded(root, () => getModuleContextTool(root, args)))
|
|
11716
|
+
);
|
|
11717
|
+
server.registerTool(
|
|
11718
|
+
"kaddo_validate_work_item_modules",
|
|
11719
|
+
{
|
|
11720
|
+
title: "Validate Work Item modules",
|
|
11721
|
+
description: "Validate that a Work Item has coherent affected_modules and sufficient cross-repo ownership.",
|
|
11722
|
+
inputSchema: { workItemId: z3.string() }
|
|
11723
|
+
},
|
|
11724
|
+
async (args) => toolText(guarded(root, () => validateWorkItemModulesTool(root, args)))
|
|
11725
|
+
);
|
|
11726
|
+
server.registerTool(
|
|
11727
|
+
"kaddo_get_work_item_context",
|
|
11728
|
+
{
|
|
11729
|
+
title: "Get Work Item context",
|
|
11730
|
+
description: "Composite context for implementing a multirepo Work Item: WI details, affected module contexts, ownership, branch strategy.",
|
|
11731
|
+
inputSchema: {
|
|
11732
|
+
workItemId: z3.string(),
|
|
11733
|
+
includeModuleContexts: z3.boolean().optional(),
|
|
11734
|
+
includeBranchStrategy: z3.boolean().optional(),
|
|
11735
|
+
includeGuardExpectations: z3.boolean().optional()
|
|
11736
|
+
}
|
|
11737
|
+
},
|
|
11738
|
+
async (args) => toolText(guarded(root, () => getWorkItemContextTool(root, args)))
|
|
11739
|
+
);
|
|
11740
|
+
server.registerTool(
|
|
11741
|
+
"kaddo_suggest_branch_strategy",
|
|
11742
|
+
{
|
|
11743
|
+
title: "Suggest branch strategy",
|
|
11744
|
+
description: "Suggest branch names, commit messages, and a checklist for a multirepo Work Item. Does NOT execute git.",
|
|
11745
|
+
inputSchema: { workItemId: z3.string() }
|
|
11746
|
+
},
|
|
11747
|
+
async (args) => toolText(guarded(root, () => suggestBranchStrategyTool(root, args)))
|
|
11748
|
+
);
|
|
11749
|
+
server.registerTool(
|
|
11750
|
+
"kaddo_export_capsule",
|
|
11751
|
+
{
|
|
11752
|
+
title: "Export Knowledge Capsule",
|
|
11753
|
+
description: "Export a Knowledge Capsule as a derived artifact under .kaddo/exports/. Supports scope=system (from core) and module=<id> (from core or module).",
|
|
11754
|
+
inputSchema: {
|
|
11755
|
+
scope: z3.string().optional(),
|
|
11756
|
+
module: z3.string().optional()
|
|
11757
|
+
}
|
|
11758
|
+
},
|
|
11759
|
+
async (args) => toolText(guarded(root, () => exportCapsuleTool(root, args)))
|
|
11760
|
+
);
|
|
11761
|
+
server.registerTool(
|
|
11762
|
+
"kaddo_modules_discover",
|
|
11763
|
+
{
|
|
11764
|
+
title: "Discover multirepo modules",
|
|
11765
|
+
description: "Discover sibling repositories configured as Kaddo modules. Does not persist without apply+confirm. Core only.",
|
|
11766
|
+
inputSchema: {
|
|
11767
|
+
apply: z3.boolean().optional(),
|
|
11768
|
+
confirm: z3.boolean().optional()
|
|
11769
|
+
}
|
|
11770
|
+
},
|
|
11771
|
+
async (args) => toolText(guarded(root, () => modulesDiscoverTool(root, args)))
|
|
11772
|
+
);
|
|
11115
11773
|
let installedSkills = [];
|
|
11116
11774
|
try {
|
|
11117
11775
|
assertKaddoProject(root);
|
package/package.json
CHANGED