@kaddo/mcp 3.114.0 → 3.116.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 +808 -145
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -371,6 +371,9 @@ function exists(p) {
|
|
|
371
371
|
function readFile(p) {
|
|
372
372
|
return fs2.readFileSync(p, "utf-8");
|
|
373
373
|
}
|
|
374
|
+
function ensureDir(dirPath) {
|
|
375
|
+
fs2.mkdirSync(dirPath, { recursive: true });
|
|
376
|
+
}
|
|
374
377
|
function readDir(dirPath) {
|
|
375
378
|
if (!exists(dirPath)) return [];
|
|
376
379
|
return fs2.readdirSync(dirPath);
|
|
@@ -386,7 +389,7 @@ function join(...parts) {
|
|
|
386
389
|
}
|
|
387
390
|
|
|
388
391
|
// ../cli/src/core/impact-report.ts
|
|
389
|
-
import
|
|
392
|
+
import matter10 from "gray-matter";
|
|
390
393
|
|
|
391
394
|
// ../cli/src/core/config.ts
|
|
392
395
|
import { z } from "zod";
|
|
@@ -485,8 +488,8 @@ function loadConfig(dir) {
|
|
|
485
488
|
const parsed = configSchema.safeParse(raw ?? {});
|
|
486
489
|
if (!parsed.success) {
|
|
487
490
|
const issues = parsed.error.issues.map((i) => {
|
|
488
|
-
const
|
|
489
|
-
return
|
|
491
|
+
const path9 = i.path.join(".");
|
|
492
|
+
return path9 ? ` - ${path9}: ${i.message}` : ` - ${i.message}`;
|
|
490
493
|
}).join("\n");
|
|
491
494
|
throw new ConfigError(`Invalid .kaddo/config.yml:
|
|
492
495
|
${issues}`);
|
|
@@ -927,11 +930,11 @@ function moduleArtifactCoverage(dir, id) {
|
|
|
927
930
|
};
|
|
928
931
|
}
|
|
929
932
|
function loadMappedModules(dir) {
|
|
930
|
-
const
|
|
931
|
-
if (!exists(
|
|
933
|
+
const path9 = join(dir, DESCRIPTOR_PATH);
|
|
934
|
+
if (!exists(path9)) return [];
|
|
932
935
|
let parsed;
|
|
933
936
|
try {
|
|
934
|
-
parsed = parseYaml4(readFile(
|
|
937
|
+
parsed = parseYaml4(readFile(path9));
|
|
935
938
|
} catch {
|
|
936
939
|
return [];
|
|
937
940
|
}
|
|
@@ -2438,7 +2441,7 @@ function sectionParagraph2(md, title) {
|
|
|
2438
2441
|
}
|
|
2439
2442
|
return "";
|
|
2440
2443
|
}
|
|
2441
|
-
function parseCapsule(id,
|
|
2444
|
+
function parseCapsule(id, path9, md) {
|
|
2442
2445
|
const { data } = matter4(md);
|
|
2443
2446
|
const updatedAt = data.updated_at ? String(data.updated_at) : void 0;
|
|
2444
2447
|
let ageDays = null;
|
|
@@ -2448,7 +2451,7 @@ function parseCapsule(id, path7, md) {
|
|
|
2448
2451
|
}
|
|
2449
2452
|
return {
|
|
2450
2453
|
id,
|
|
2451
|
-
path:
|
|
2454
|
+
path: path9,
|
|
2452
2455
|
system: data.system ? String(data.system) : id,
|
|
2453
2456
|
owner: data.owner ? String(data.owner) : void 0,
|
|
2454
2457
|
updatedAt,
|
|
@@ -2590,8 +2593,145 @@ function discoverInitiatives(dir) {
|
|
|
2590
2593
|
return out.sort((a2, b2) => a2.id.localeCompare(b2.id));
|
|
2591
2594
|
}
|
|
2592
2595
|
|
|
2596
|
+
// ../cli/src/core/resources.ts
|
|
2597
|
+
import matter6 from "gray-matter";
|
|
2598
|
+
var RESOURCE_ARTIFACT_TYPE = "project-resource";
|
|
2599
|
+
var RESOURCE_TYPES = [
|
|
2600
|
+
"database",
|
|
2601
|
+
"cloud",
|
|
2602
|
+
"api",
|
|
2603
|
+
"queue",
|
|
2604
|
+
"storage",
|
|
2605
|
+
"repository",
|
|
2606
|
+
"platform",
|
|
2607
|
+
"service",
|
|
2608
|
+
"other"
|
|
2609
|
+
];
|
|
2610
|
+
var INTERFACE_TYPES = ["cli", "mcp", "api", "sql", "sdk", "iac", "console", "other"];
|
|
2611
|
+
function str(v) {
|
|
2612
|
+
return typeof v === "string" && v.trim() ? v.trim() : null;
|
|
2613
|
+
}
|
|
2614
|
+
function strArray(v) {
|
|
2615
|
+
if (Array.isArray(v)) return v.map((x2) => String(x2).trim()).filter(Boolean);
|
|
2616
|
+
if (typeof v === "string" && v.trim()) return [v.trim()];
|
|
2617
|
+
return [];
|
|
2618
|
+
}
|
|
2619
|
+
function parseInterfaces(v) {
|
|
2620
|
+
if (!Array.isArray(v)) return [];
|
|
2621
|
+
const out = [];
|
|
2622
|
+
for (const raw of v) {
|
|
2623
|
+
if (!raw || typeof raw !== "object") continue;
|
|
2624
|
+
const o2 = raw;
|
|
2625
|
+
const type = str(o2.type);
|
|
2626
|
+
if (!type) continue;
|
|
2627
|
+
out.push({
|
|
2628
|
+
type,
|
|
2629
|
+
tool: str(o2.tool),
|
|
2630
|
+
provider: str(o2.provider),
|
|
2631
|
+
purpose: str(o2.purpose),
|
|
2632
|
+
operations: strArray(o2.operations),
|
|
2633
|
+
environments: strArray(o2.environments),
|
|
2634
|
+
constraints: str(o2.constraints)
|
|
2635
|
+
});
|
|
2636
|
+
}
|
|
2637
|
+
return out;
|
|
2638
|
+
}
|
|
2639
|
+
function parseBoundaries(v) {
|
|
2640
|
+
if (!v || typeof v !== "object" || Array.isArray(v)) return {};
|
|
2641
|
+
const out = {};
|
|
2642
|
+
for (const [env, val] of Object.entries(v)) {
|
|
2643
|
+
const list3 = strArray(val);
|
|
2644
|
+
if (list3.length > 0) out[env] = list3;
|
|
2645
|
+
}
|
|
2646
|
+
return out;
|
|
2647
|
+
}
|
|
2648
|
+
function parseAuth(v) {
|
|
2649
|
+
if (!v || typeof v !== "object" || Array.isArray(v)) return { mode: null, refs: [] };
|
|
2650
|
+
const o2 = v;
|
|
2651
|
+
const refs = strArray(o2.refs).filter((r2) => !r2.includes("=") && /^[A-Za-z0-9_.-]+$/.test(r2));
|
|
2652
|
+
return { mode: str(o2.mode), refs };
|
|
2653
|
+
}
|
|
2654
|
+
function parseScope(v) {
|
|
2655
|
+
if (!v || typeof v !== "object" || Array.isArray(v)) return null;
|
|
2656
|
+
const o2 = v;
|
|
2657
|
+
const type = str(o2.type);
|
|
2658
|
+
if (!type) return null;
|
|
2659
|
+
return { type, module: str(o2.module) };
|
|
2660
|
+
}
|
|
2661
|
+
function purposeFromBody(body) {
|
|
2662
|
+
const m2 = body.match(/^#{1,2}\s+Purpose\s*\n+([\s\S]*?)(?=\n#{1,2}\s|\s*$)/m);
|
|
2663
|
+
const text3 = m2 ? m2[1].trim() : "";
|
|
2664
|
+
return text3 || null;
|
|
2665
|
+
}
|
|
2666
|
+
function toDetail(filePath, relPath, raw) {
|
|
2667
|
+
const parsed = matter6(raw);
|
|
2668
|
+
const fm = parsed.data ?? {};
|
|
2669
|
+
const body = parsed.content.trim();
|
|
2670
|
+
const id = str(fm.id) ?? "";
|
|
2671
|
+
return {
|
|
2672
|
+
id,
|
|
2673
|
+
title: str(fm.title) ?? id,
|
|
2674
|
+
resourceType: str(fm.resource_type),
|
|
2675
|
+
provider: str(fm.provider),
|
|
2676
|
+
environments: strArray(fm.environments),
|
|
2677
|
+
scope: parseScope(fm.scope),
|
|
2678
|
+
modules: strArray(fm.modules),
|
|
2679
|
+
path: relPath,
|
|
2680
|
+
purpose: str(fm.purpose) ?? purposeFromBody(body),
|
|
2681
|
+
interfaces: parseInterfaces(fm.access_interfaces),
|
|
2682
|
+
boundaries: parseBoundaries(fm.access_boundaries),
|
|
2683
|
+
...(() => {
|
|
2684
|
+
const a2 = parseAuth(fm.authentication);
|
|
2685
|
+
return { authRefs: a2.refs, authMode: a2.mode };
|
|
2686
|
+
})(),
|
|
2687
|
+
markdownBody: body
|
|
2688
|
+
};
|
|
2689
|
+
}
|
|
2690
|
+
function getResources(dir) {
|
|
2691
|
+
return discoverKnowledge(dir).filter((a2) => a2.type === RESOURCE_ARTIFACT_TYPE).map((a2) => {
|
|
2692
|
+
const fm = a2.rawFrontmatter;
|
|
2693
|
+
return {
|
|
2694
|
+
id: a2.id || str(fm.id) || a2.title,
|
|
2695
|
+
title: a2.title || str(fm.title) || a2.id,
|
|
2696
|
+
resourceType: str(fm.resource_type),
|
|
2697
|
+
provider: str(fm.provider),
|
|
2698
|
+
environments: strArray(fm.environments),
|
|
2699
|
+
scope: parseScope(fm.scope),
|
|
2700
|
+
modules: strArray(fm.modules),
|
|
2701
|
+
path: a2.relPath
|
|
2702
|
+
};
|
|
2703
|
+
}).sort((x2, y) => x2.id.localeCompare(y.id));
|
|
2704
|
+
}
|
|
2705
|
+
function getResource(dir, id) {
|
|
2706
|
+
const match = discoverKnowledge(dir).find(
|
|
2707
|
+
(a2) => a2.type === RESOURCE_ARTIFACT_TYPE && (a2.id === id || a2.title === id)
|
|
2708
|
+
);
|
|
2709
|
+
if (!match) return null;
|
|
2710
|
+
const raw = readFile(match.filePath);
|
|
2711
|
+
return toDetail(match.filePath, match.relPath, raw);
|
|
2712
|
+
}
|
|
2713
|
+
function parseWorkItemResources(fm) {
|
|
2714
|
+
const v = fm.resources;
|
|
2715
|
+
if (!Array.isArray(v)) return [];
|
|
2716
|
+
const out = [];
|
|
2717
|
+
for (const raw of v) {
|
|
2718
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) continue;
|
|
2719
|
+
const o2 = raw;
|
|
2720
|
+
const id = str(o2.id);
|
|
2721
|
+
if (!id) continue;
|
|
2722
|
+
out.push({ id, role: str(o2.role) ?? "affected" });
|
|
2723
|
+
}
|
|
2724
|
+
return out;
|
|
2725
|
+
}
|
|
2726
|
+
|
|
2593
2727
|
// ../cli/src/core/graph.ts
|
|
2594
2728
|
var KNOWLEDGE3 = "knowledge";
|
|
2729
|
+
var RESOURCE_ROLE_EDGE = {
|
|
2730
|
+
affected: "affects",
|
|
2731
|
+
implementation: "uses",
|
|
2732
|
+
validation: "validates_with",
|
|
2733
|
+
delivery: "delivers_through"
|
|
2734
|
+
};
|
|
2595
2735
|
var ACTIVE_STATUSES = ["draft", "ready", "in-progress", "blocked"];
|
|
2596
2736
|
var ALL_STATUSES = ["draft", "ready", "in-progress", "blocked", "completed"];
|
|
2597
2737
|
function scopeStatuses(scope) {
|
|
@@ -2628,9 +2768,9 @@ function buildGraph(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
|
|
|
2628
2768
|
];
|
|
2629
2769
|
const presentLayers = [];
|
|
2630
2770
|
for (const layer2 of layerDocs) {
|
|
2631
|
-
const
|
|
2632
|
-
if (
|
|
2633
|
-
addNode({ id: layer2.id, type: layer2.type, label: layer2.label, path:
|
|
2771
|
+
const path9 = layer2.files.map((f3) => `${KNOWLEDGE3}/${f3}`).find((rel) => exists(join(dir, rel)));
|
|
2772
|
+
if (path9) {
|
|
2773
|
+
addNode({ id: layer2.id, type: layer2.type, label: layer2.label, path: path9 });
|
|
2634
2774
|
presentLayers.push(layer2.id);
|
|
2635
2775
|
}
|
|
2636
2776
|
}
|
|
@@ -2670,6 +2810,24 @@ function buildGraph(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
|
|
|
2670
2810
|
if (cand.materializedAs) addEdge(candId, `wi:${cand.materializedAs}`, "materialized_as");
|
|
2671
2811
|
}
|
|
2672
2812
|
}
|
|
2813
|
+
const resourceTitleById = /* @__PURE__ */ new Map();
|
|
2814
|
+
for (const r2 of getResources(dir)) {
|
|
2815
|
+
resourceTitleById.set(r2.id, r2.title);
|
|
2816
|
+
const resId = `resource:${r2.id}`;
|
|
2817
|
+
addNode({
|
|
2818
|
+
id: resId,
|
|
2819
|
+
type: "project-resource",
|
|
2820
|
+
label: r2.title ? `${r2.id} ${r2.title}`.trim() : r2.id,
|
|
2821
|
+
path: r2.path
|
|
2822
|
+
});
|
|
2823
|
+
const depModules = new Set(r2.modules);
|
|
2824
|
+
if (r2.scope?.type === "module" && r2.scope.module) depModules.add(r2.scope.module);
|
|
2825
|
+
for (const mod of depModules) {
|
|
2826
|
+
const modId = `module:${mod}`;
|
|
2827
|
+
addNode({ id: modId, type: "module", label: mod });
|
|
2828
|
+
addEdge(modId, resId, "depends_on");
|
|
2829
|
+
}
|
|
2830
|
+
}
|
|
2673
2831
|
for (const wi of selectedWIs) {
|
|
2674
2832
|
const id = wi.id || wi.title;
|
|
2675
2833
|
if (!id || !id.trim()) continue;
|
|
@@ -2694,6 +2852,12 @@ function buildGraph(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
|
|
|
2694
2852
|
addNode({ id: capId, type: "capability", label: cap });
|
|
2695
2853
|
addEdge(wiNodeId, capId, "implements");
|
|
2696
2854
|
}
|
|
2855
|
+
for (const rel of parseWorkItemResources(wi.rawFrontmatter)) {
|
|
2856
|
+
const resId = `resource:${rel.id}`;
|
|
2857
|
+
const title = resourceTitleById.get(rel.id);
|
|
2858
|
+
addNode({ id: resId, type: "project-resource", label: title ? `${rel.id} ${title}`.trim() : rel.id });
|
|
2859
|
+
addEdge(wiNodeId, resId, RESOURCE_ROLE_EDGE[rel.role] ?? "uses");
|
|
2860
|
+
}
|
|
2697
2861
|
for (const dec of wi.decisions) {
|
|
2698
2862
|
if (!dec || !dec.trim()) continue;
|
|
2699
2863
|
const adrId = `adr:${dec}`;
|
|
@@ -3072,7 +3236,7 @@ function loadGraphHints(dir) {
|
|
|
3072
3236
|
}
|
|
3073
3237
|
|
|
3074
3238
|
// ../cli/src/services/installed-skills.ts
|
|
3075
|
-
import
|
|
3239
|
+
import matter7 from "gray-matter";
|
|
3076
3240
|
var SKILLS_DIR = join("knowledge", "skills");
|
|
3077
3241
|
function discoverInstalledSkills(dir) {
|
|
3078
3242
|
const base = join(dir, SKILLS_DIR);
|
|
@@ -3082,7 +3246,7 @@ function discoverInstalledSkills(dir) {
|
|
|
3082
3246
|
const skillFile = join(base, entry, "skill.md");
|
|
3083
3247
|
if (!isFile(skillFile)) continue;
|
|
3084
3248
|
try {
|
|
3085
|
-
const { data } =
|
|
3249
|
+
const { data } = matter7(readFile(skillFile));
|
|
3086
3250
|
if (data.type && String(data.type) !== "skill") continue;
|
|
3087
3251
|
const id = String(data.id ?? entry);
|
|
3088
3252
|
out.push({
|
|
@@ -4238,10 +4402,10 @@ function resolveNextStep(dir, now = /* @__PURE__ */ new Date()) {
|
|
|
4238
4402
|
const q3 = (rel) => analyzeKnowledgeArtifact(dir, rel);
|
|
4239
4403
|
const resolveAgent = (agent) => {
|
|
4240
4404
|
const file = agent.endsWith(".md") ? agent : `${agent}.md`;
|
|
4241
|
-
const
|
|
4242
|
-
const installed = isFile(join(dir,
|
|
4405
|
+
const path9 = agentInstallPath(file);
|
|
4406
|
+
const installed = isFile(join(dir, path9));
|
|
4243
4407
|
const group = agentGroupOf(file);
|
|
4244
|
-
return { agentPath:
|
|
4408
|
+
return { agentPath: path9, agentInstalled: installed, installCommand: installed ? void 0 : `kaddo add agents --group ${group}` };
|
|
4245
4409
|
};
|
|
4246
4410
|
const moduleRepo = isModule(config);
|
|
4247
4411
|
const coreRepo = isCore(config);
|
|
@@ -4671,7 +4835,7 @@ function deriveDeliveryCompletedStatus(dir) {
|
|
|
4671
4835
|
}
|
|
4672
4836
|
|
|
4673
4837
|
// ../cli/src/core/assets.ts
|
|
4674
|
-
import
|
|
4838
|
+
import matter8 from "gray-matter";
|
|
4675
4839
|
|
|
4676
4840
|
// ../cli/src/core/version.ts
|
|
4677
4841
|
import { fileURLToPath } from "url";
|
|
@@ -4826,8 +4990,13 @@ dependencies.
|
|
|
4826
4990
|
flags, content/copy, authentication/authorization, notifications, analytics, documentation,
|
|
4827
4991
|
operations/release \u2014 as affected, reviewed-not-affected, unknown, or not-applicable.
|
|
4828
4992
|
4. **Module review** \u2014 for multirepo, evaluate each mapped module with the same statuses.
|
|
4829
|
-
5. **
|
|
4830
|
-
|
|
4993
|
+
5. **Resource review** \u2014 ask: does this change need to observe, modify, validate or deliver against a
|
|
4994
|
+
known external resource of the project (a Project Resource under \`knowledge/tech/resources/\` \u2014
|
|
4995
|
+
database, cloud, API, queue, storage\u2026)? If so, declare it in the WI front matter \`resources:\` with
|
|
4996
|
+
a role (\`affected\`, \`implementation\`, \`validation\`, \`delivery\`). Resources are external systems,
|
|
4997
|
+
distinct from \`affected_modules\` (code). Not every WI needs resources (e.g. a CSS change does not).
|
|
4998
|
+
6. **Completeness review** \u2014 confirm: outcome covered, journey covered, modules assessed, resources
|
|
4999
|
+
assessed, unknowns visible, acceptance criteria end-to-end, scope and out-of-scope coherent.
|
|
4831
5000
|
|
|
4832
5001
|
## Rules
|
|
4833
5002
|
|
|
@@ -4838,6 +5007,8 @@ dependencies.
|
|
|
4838
5007
|
- Include at least one end-to-end acceptance criterion for user-facing changes.
|
|
4839
5008
|
- Do not reduce a product intent to the first technical implementation found.
|
|
4840
5009
|
- Evaluate surfaces and modules before proposing files.
|
|
5010
|
+
- Consider Project Resources when the scope justifies it; never invent resources not backed by
|
|
5011
|
+
project knowledge, and keep \`resources\` (external systems) separate from \`affected_modules\` (code).
|
|
4841
5012
|
- This skill refines scope and acceptance criteria but does not approve implementation readiness.
|
|
4842
5013
|
Readiness requires human confirmation through \`kaddo ready\` or the MCP \`mark_work_item_ready\` action.
|
|
4843
5014
|
|
|
@@ -5989,6 +6160,16 @@ supporting notes as \`knowledge/tech/discovery/architecture-notes.md\` and decis
|
|
|
5989
6160
|
under \`knowledge/tech/decisions/\`. Kaddo still reads the legacy root locations for backward
|
|
5990
6161
|
compatibility, but new output should use \`discovery/\`.
|
|
5991
6162
|
|
|
6163
|
+
## Project Resource candidates
|
|
6164
|
+
|
|
6165
|
+
When the knowledge describes an external system the project depends on (a database, cloud account,
|
|
6166
|
+
API, queue, storage, service\u2026), you may propose it as a **candidate** Project Resource \u2014 never create
|
|
6167
|
+
it silently. Discovery \u2260 creation: present candidates for human review and materialize only through
|
|
6168
|
+
\`kaddo_create_resource\` / \`kaddo resources create\` with explicit confirmation. Ground every
|
|
6169
|
+
candidate in the knowledge (no guessing from ambiguous names). Model the stable system, not its access
|
|
6170
|
+
interface (CLI/MCP/SQL/API are interfaces); keep \`resources\` separate from \`affected_modules\`; and
|
|
6171
|
+
never include secret values \u2014 only reference names.
|
|
6172
|
+
|
|
5992
6173
|
## Quality Checklist
|
|
5993
6174
|
|
|
5994
6175
|
- Every component is backed by evidence from the context pack.
|
|
@@ -7877,7 +8058,7 @@ function canonicalSkills() {
|
|
|
7877
8058
|
}
|
|
7878
8059
|
function versionOf(content) {
|
|
7879
8060
|
try {
|
|
7880
|
-
const v =
|
|
8061
|
+
const v = matter8(content).data.version;
|
|
7881
8062
|
return v === void 0 || v === null ? null : String(v);
|
|
7882
8063
|
} catch {
|
|
7883
8064
|
return null;
|
|
@@ -8649,7 +8830,7 @@ function renderRouteCompact(route) {
|
|
|
8649
8830
|
}
|
|
8650
8831
|
|
|
8651
8832
|
// ../cli/src/core/metadata-health.ts
|
|
8652
|
-
import
|
|
8833
|
+
import matter9 from "gray-matter";
|
|
8653
8834
|
var KNOWLEDGE_FILES2 = [
|
|
8654
8835
|
"knowledge/business/business.md",
|
|
8655
8836
|
"knowledge/product/product.md",
|
|
@@ -8669,7 +8850,7 @@ function analyzeMetadataHealth(dir) {
|
|
|
8669
8850
|
let data;
|
|
8670
8851
|
try {
|
|
8671
8852
|
const raw = readFile(p);
|
|
8672
|
-
data =
|
|
8853
|
+
data = matter9(raw).data;
|
|
8673
8854
|
} catch {
|
|
8674
8855
|
continue;
|
|
8675
8856
|
}
|
|
@@ -8701,7 +8882,7 @@ function analyzeMetadataHealth(dir) {
|
|
|
8701
8882
|
if (isFile(p) && entry.endsWith(".md")) {
|
|
8702
8883
|
try {
|
|
8703
8884
|
const raw = readFile(p);
|
|
8704
|
-
const fm =
|
|
8885
|
+
const fm = matter9(raw).data;
|
|
8705
8886
|
if (fm.status === "done") {
|
|
8706
8887
|
const rel = p.replace(dir + "/", "").replace(dir + "\\", "").replace(/\\/g, "/");
|
|
8707
8888
|
findings.push({
|
|
@@ -9569,7 +9750,7 @@ function buildGuardHistory(dir) {
|
|
|
9569
9750
|
const d3 = hotspotDir(t.code_path);
|
|
9570
9751
|
hotspotMap.set(d3, (hotspotMap.get(d3) ?? 0) + 1);
|
|
9571
9752
|
}
|
|
9572
|
-
const hotspots = [...hotspotMap.entries()].map(([
|
|
9753
|
+
const hotspots = [...hotspotMap.entries()].map(([path9, warnings]) => ({ path: path9, warnings })).sort((a2, b2) => b2.warnings - a2.warnings);
|
|
9573
9754
|
return {
|
|
9574
9755
|
available: true,
|
|
9575
9756
|
total_runs: sorted.length,
|
|
@@ -9656,7 +9837,7 @@ function buildImpactReport(dir, opts = {}, now = /* @__PURE__ */ new Date()) {
|
|
|
9656
9837
|
}
|
|
9657
9838
|
}
|
|
9658
9839
|
try {
|
|
9659
|
-
const body =
|
|
9840
|
+
const body = matter10(readFile(wi.filePath)).content;
|
|
9660
9841
|
if (hasSection(body, /acceptance|criterios de aceptaci/i)) withAcceptance++;
|
|
9661
9842
|
else gaps.missing_acceptance_criteria.push(gapItem(wi, "Add an `## Acceptance Criteria` section."));
|
|
9662
9843
|
if (hasSection(body, /definition of done|^#{1,6}\s*dod\b|definici[oó]n de (terminado|hecho)/i)) withDoD++;
|
|
@@ -10369,7 +10550,7 @@ function serializeDriftJson(r2) {
|
|
|
10369
10550
|
}
|
|
10370
10551
|
|
|
10371
10552
|
// src/workitems.ts
|
|
10372
|
-
import
|
|
10553
|
+
import matter11 from "gray-matter";
|
|
10373
10554
|
var VALID_SOURCES2 = /* @__PURE__ */ new Set(["manual", "roadmap", "jira", "github", "notion", "xlsx", "csv", "api", "external", "unknown"]);
|
|
10374
10555
|
function optStr3(v) {
|
|
10375
10556
|
return typeof v === "string" && v.trim() ? v.trim() : void 0;
|
|
@@ -10392,7 +10573,7 @@ function parseSource(data) {
|
|
|
10392
10573
|
return { type: "unknown", inferred: true };
|
|
10393
10574
|
}
|
|
10394
10575
|
var ACTIVE_STATES2 = /* @__PURE__ */ new Set(["draft", "ready", "in-progress", "blocked"]);
|
|
10395
|
-
function
|
|
10576
|
+
function strArray2(v) {
|
|
10396
10577
|
return Array.isArray(v) ? v.map((x2) => String(x2)).filter(Boolean) : [];
|
|
10397
10578
|
}
|
|
10398
10579
|
function lifecycleOf(data, relPath) {
|
|
@@ -10460,15 +10641,15 @@ function listWorkItems(root) {
|
|
|
10460
10641
|
if (raw === null) continue;
|
|
10461
10642
|
let parsed;
|
|
10462
10643
|
try {
|
|
10463
|
-
parsed =
|
|
10644
|
+
parsed = matter11(raw);
|
|
10464
10645
|
} catch {
|
|
10465
10646
|
continue;
|
|
10466
10647
|
}
|
|
10467
10648
|
const data = parsed.data;
|
|
10468
10649
|
if (!data.type) continue;
|
|
10469
10650
|
const status = lifecycleOf(data, rel);
|
|
10470
|
-
const code =
|
|
10471
|
-
const domains =
|
|
10651
|
+
const code = strArray2(data.code);
|
|
10652
|
+
const domains = strArray2(data.domains);
|
|
10472
10653
|
const source = parseSource(data);
|
|
10473
10654
|
const readiness = status === "draft" ? assessReadiness(data, parsed.content, code, domains, source) : void 0;
|
|
10474
10655
|
items.push({
|
|
@@ -10480,9 +10661,9 @@ function listWorkItems(root) {
|
|
|
10480
10661
|
path: rel,
|
|
10481
10662
|
summary: String(data.summary ?? "") || firstParagraph2(parsed.content),
|
|
10482
10663
|
code,
|
|
10483
|
-
capabilities:
|
|
10484
|
-
decisions:
|
|
10485
|
-
capsules:
|
|
10664
|
+
capabilities: strArray2(data.capabilities),
|
|
10665
|
+
decisions: strArray2(data.decisions),
|
|
10666
|
+
capsules: strArray2(data.capsules),
|
|
10486
10667
|
source,
|
|
10487
10668
|
domains,
|
|
10488
10669
|
readiness,
|
|
@@ -10928,14 +11109,14 @@ function getPrompt(root, name) {
|
|
|
10928
11109
|
}
|
|
10929
11110
|
|
|
10930
11111
|
// src/tools.ts
|
|
10931
|
-
import
|
|
11112
|
+
import matter15 from "gray-matter";
|
|
10932
11113
|
|
|
10933
11114
|
// ../cli/dist/core.js
|
|
10934
11115
|
import fs3 from "fs";
|
|
10935
11116
|
import path3 from "path";
|
|
10936
11117
|
import { z as z3 } from "zod";
|
|
10937
11118
|
import { parse as parseYaml10 } from "yaml";
|
|
10938
|
-
import
|
|
11119
|
+
import matter12 from "gray-matter";
|
|
10939
11120
|
import matter22 from "gray-matter";
|
|
10940
11121
|
import { parse as parseYaml62, stringify as stringifyYaml32 } from "yaml";
|
|
10941
11122
|
import { parse as parseYaml22 } from "yaml";
|
|
@@ -10944,25 +11125,27 @@ import { parse as parseYaml52, stringify as stringifyYaml22 } from "yaml";
|
|
|
10944
11125
|
import { parse as parseYaml42, stringify as stringifyYaml4 } from "yaml";
|
|
10945
11126
|
import matter32 from "gray-matter";
|
|
10946
11127
|
import matter42 from "gray-matter";
|
|
10947
|
-
import { parse as parseYaml72 } from "yaml";
|
|
10948
11128
|
import matter52 from "gray-matter";
|
|
11129
|
+
import { parse as parseYaml72 } from "yaml";
|
|
11130
|
+
import matter62 from "gray-matter";
|
|
10949
11131
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
10950
11132
|
import { dirname as dirname2, join as join22, parse as parse2 } from "path";
|
|
10951
11133
|
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
10952
|
-
import matter62 from "gray-matter";
|
|
10953
11134
|
import matter72 from "gray-matter";
|
|
10954
|
-
import { parse as parseYaml82, stringify as stringifyYaml42 } from "yaml";
|
|
10955
|
-
import fs32 from "fs";
|
|
10956
|
-
import path32 from "path";
|
|
10957
|
-
import crypto2 from "crypto";
|
|
10958
11135
|
import matter82 from "gray-matter";
|
|
10959
|
-
import crypto3 from "crypto";
|
|
10960
|
-
import fs4 from "fs";
|
|
10961
11136
|
import matter92 from "gray-matter";
|
|
11137
|
+
import { parse as parseYaml82, stringify as stringifyYaml42 } from "yaml";
|
|
11138
|
+
import fs4 from "fs";
|
|
11139
|
+
import path4 from "path";
|
|
11140
|
+
import crypto2 from "crypto";
|
|
10962
11141
|
import matter102 from "gray-matter";
|
|
11142
|
+
import crypto3 from "crypto";
|
|
11143
|
+
import fs5 from "fs";
|
|
11144
|
+
import matter112 from "gray-matter";
|
|
11145
|
+
import matter122 from "gray-matter";
|
|
10963
11146
|
import { parse as parseYaml92, stringify as stringifyYaml5 } from "yaml";
|
|
10964
11147
|
import { parse as parseYaml102, stringify as stringifyYaml6 } from "yaml";
|
|
10965
|
-
import
|
|
11148
|
+
import matter13 from "gray-matter";
|
|
10966
11149
|
function exists2(p) {
|
|
10967
11150
|
return fs3.existsSync(p);
|
|
10968
11151
|
}
|
|
@@ -11073,8 +11256,8 @@ function loadConfig2(dir) {
|
|
|
11073
11256
|
const parsed = configSchema2.safeParse(raw ?? {});
|
|
11074
11257
|
if (!parsed.success) {
|
|
11075
11258
|
const issues = parsed.error.issues.map((i) => {
|
|
11076
|
-
const
|
|
11077
|
-
return
|
|
11259
|
+
const path52 = i.path.join(".");
|
|
11260
|
+
return path52 ? ` - ${path52}: ${i.message}` : ` - ${i.message}`;
|
|
11078
11261
|
}).join("\n");
|
|
11079
11262
|
throw new ConfigError2(`Invalid .kaddo/config.yml:
|
|
11080
11263
|
${issues}`);
|
|
@@ -11130,7 +11313,7 @@ function parseImpactAnalysis2(data) {
|
|
|
11130
11313
|
}
|
|
11131
11314
|
function parseArtifact2(filePath, raw) {
|
|
11132
11315
|
try {
|
|
11133
|
-
const { data } =
|
|
11316
|
+
const { data } = matter12(raw);
|
|
11134
11317
|
const globs = Array.isArray(data.code) ? data.code.filter(Boolean) : [];
|
|
11135
11318
|
return {
|
|
11136
11319
|
filePath,
|
|
@@ -11278,11 +11461,11 @@ function moduleArtifactCoverage2(dir, id) {
|
|
|
11278
11461
|
};
|
|
11279
11462
|
}
|
|
11280
11463
|
function loadMappedModules2(dir) {
|
|
11281
|
-
const
|
|
11282
|
-
if (!exists2(
|
|
11464
|
+
const path52 = join3(dir, DESCRIPTOR_PATH2);
|
|
11465
|
+
if (!exists2(path52)) return [];
|
|
11283
11466
|
let parsed;
|
|
11284
11467
|
try {
|
|
11285
|
-
parsed = parseYaml32(readFile2(
|
|
11468
|
+
parsed = parseYaml32(readFile2(path52));
|
|
11286
11469
|
} catch {
|
|
11287
11470
|
return [];
|
|
11288
11471
|
}
|
|
@@ -12684,7 +12867,129 @@ function suggestInitiativesForExternalItem(dir, integration, externalId) {
|
|
|
12684
12867
|
)
|
|
12685
12868
|
);
|
|
12686
12869
|
}
|
|
12870
|
+
var RESOURCE_ARTIFACT_TYPE2 = "project-resource";
|
|
12871
|
+
function str2(v) {
|
|
12872
|
+
return typeof v === "string" && v.trim() ? v.trim() : null;
|
|
12873
|
+
}
|
|
12874
|
+
function strArray3(v) {
|
|
12875
|
+
if (Array.isArray(v)) return v.map((x2) => String(x2).trim()).filter(Boolean);
|
|
12876
|
+
if (typeof v === "string" && v.trim()) return [v.trim()];
|
|
12877
|
+
return [];
|
|
12878
|
+
}
|
|
12879
|
+
function parseInterfaces2(v) {
|
|
12880
|
+
if (!Array.isArray(v)) return [];
|
|
12881
|
+
const out = [];
|
|
12882
|
+
for (const raw of v) {
|
|
12883
|
+
if (!raw || typeof raw !== "object") continue;
|
|
12884
|
+
const o2 = raw;
|
|
12885
|
+
const type = str2(o2.type);
|
|
12886
|
+
if (!type) continue;
|
|
12887
|
+
out.push({
|
|
12888
|
+
type,
|
|
12889
|
+
tool: str2(o2.tool),
|
|
12890
|
+
provider: str2(o2.provider),
|
|
12891
|
+
purpose: str2(o2.purpose),
|
|
12892
|
+
operations: strArray3(o2.operations),
|
|
12893
|
+
environments: strArray3(o2.environments),
|
|
12894
|
+
constraints: str2(o2.constraints)
|
|
12895
|
+
});
|
|
12896
|
+
}
|
|
12897
|
+
return out;
|
|
12898
|
+
}
|
|
12899
|
+
function parseBoundaries2(v) {
|
|
12900
|
+
if (!v || typeof v !== "object" || Array.isArray(v)) return {};
|
|
12901
|
+
const out = {};
|
|
12902
|
+
for (const [env, val] of Object.entries(v)) {
|
|
12903
|
+
const list22 = strArray3(val);
|
|
12904
|
+
if (list22.length > 0) out[env] = list22;
|
|
12905
|
+
}
|
|
12906
|
+
return out;
|
|
12907
|
+
}
|
|
12908
|
+
function parseAuth2(v) {
|
|
12909
|
+
if (!v || typeof v !== "object" || Array.isArray(v)) return { mode: null, refs: [] };
|
|
12910
|
+
const o2 = v;
|
|
12911
|
+
const refs = strArray3(o2.refs).filter((r2) => !r2.includes("=") && /^[A-Za-z0-9_.-]+$/.test(r2));
|
|
12912
|
+
return { mode: str2(o2.mode), refs };
|
|
12913
|
+
}
|
|
12914
|
+
function parseScope2(v) {
|
|
12915
|
+
if (!v || typeof v !== "object" || Array.isArray(v)) return null;
|
|
12916
|
+
const o2 = v;
|
|
12917
|
+
const type = str2(o2.type);
|
|
12918
|
+
if (!type) return null;
|
|
12919
|
+
return { type, module: str2(o2.module) };
|
|
12920
|
+
}
|
|
12921
|
+
function purposeFromBody2(body) {
|
|
12922
|
+
const m2 = body.match(/^#{1,2}\s+Purpose\s*\n+([\s\S]*?)(?=\n#{1,2}\s|\s*$)/m);
|
|
12923
|
+
const text22 = m2 ? m2[1].trim() : "";
|
|
12924
|
+
return text22 || null;
|
|
12925
|
+
}
|
|
12926
|
+
function toDetail2(filePath, relPath, raw) {
|
|
12927
|
+
const parsed = matter42(raw);
|
|
12928
|
+
const fm = parsed.data ?? {};
|
|
12929
|
+
const body = parsed.content.trim();
|
|
12930
|
+
const id = str2(fm.id) ?? "";
|
|
12931
|
+
return {
|
|
12932
|
+
id,
|
|
12933
|
+
title: str2(fm.title) ?? id,
|
|
12934
|
+
resourceType: str2(fm.resource_type),
|
|
12935
|
+
provider: str2(fm.provider),
|
|
12936
|
+
environments: strArray3(fm.environments),
|
|
12937
|
+
scope: parseScope2(fm.scope),
|
|
12938
|
+
modules: strArray3(fm.modules),
|
|
12939
|
+
path: relPath,
|
|
12940
|
+
purpose: str2(fm.purpose) ?? purposeFromBody2(body),
|
|
12941
|
+
interfaces: parseInterfaces2(fm.access_interfaces),
|
|
12942
|
+
boundaries: parseBoundaries2(fm.access_boundaries),
|
|
12943
|
+
...(() => {
|
|
12944
|
+
const a2 = parseAuth2(fm.authentication);
|
|
12945
|
+
return { authRefs: a2.refs, authMode: a2.mode };
|
|
12946
|
+
})(),
|
|
12947
|
+
markdownBody: body
|
|
12948
|
+
};
|
|
12949
|
+
}
|
|
12950
|
+
function getResources2(dir) {
|
|
12951
|
+
return discoverKnowledge2(dir).filter((a2) => a2.type === RESOURCE_ARTIFACT_TYPE2).map((a2) => {
|
|
12952
|
+
const fm = a2.rawFrontmatter;
|
|
12953
|
+
return {
|
|
12954
|
+
id: a2.id || str2(fm.id) || a2.title,
|
|
12955
|
+
title: a2.title || str2(fm.title) || a2.id,
|
|
12956
|
+
resourceType: str2(fm.resource_type),
|
|
12957
|
+
provider: str2(fm.provider),
|
|
12958
|
+
environments: strArray3(fm.environments),
|
|
12959
|
+
scope: parseScope2(fm.scope),
|
|
12960
|
+
modules: strArray3(fm.modules),
|
|
12961
|
+
path: a2.relPath
|
|
12962
|
+
};
|
|
12963
|
+
}).sort((x2, y) => x2.id.localeCompare(y.id));
|
|
12964
|
+
}
|
|
12965
|
+
function getResource2(dir, id) {
|
|
12966
|
+
const match = discoverKnowledge2(dir).find(
|
|
12967
|
+
(a2) => a2.type === RESOURCE_ARTIFACT_TYPE2 && (a2.id === id || a2.title === id)
|
|
12968
|
+
);
|
|
12969
|
+
if (!match) return null;
|
|
12970
|
+
const raw = readFile2(match.filePath);
|
|
12971
|
+
return toDetail2(match.filePath, match.relPath, raw);
|
|
12972
|
+
}
|
|
12973
|
+
function parseWorkItemResources2(fm) {
|
|
12974
|
+
const v = fm.resources;
|
|
12975
|
+
if (!Array.isArray(v)) return [];
|
|
12976
|
+
const out = [];
|
|
12977
|
+
for (const raw of v) {
|
|
12978
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) continue;
|
|
12979
|
+
const o2 = raw;
|
|
12980
|
+
const id = str2(o2.id);
|
|
12981
|
+
if (!id) continue;
|
|
12982
|
+
out.push({ id, role: str2(o2.role) ?? "affected" });
|
|
12983
|
+
}
|
|
12984
|
+
return out;
|
|
12985
|
+
}
|
|
12687
12986
|
var KNOWLEDGE22 = "knowledge";
|
|
12987
|
+
var RESOURCE_ROLE_EDGE2 = {
|
|
12988
|
+
affected: "affects",
|
|
12989
|
+
implementation: "uses",
|
|
12990
|
+
validation: "validates_with",
|
|
12991
|
+
delivery: "delivers_through"
|
|
12992
|
+
};
|
|
12688
12993
|
var ACTIVE_STATUSES2 = ["draft", "ready", "in-progress", "blocked"];
|
|
12689
12994
|
var ALL_STATUSES2 = ["draft", "ready", "in-progress", "blocked", "completed"];
|
|
12690
12995
|
function scopeStatuses2(scope) {
|
|
@@ -12721,9 +13026,9 @@ function buildGraph2(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
|
|
|
12721
13026
|
];
|
|
12722
13027
|
const presentLayers = [];
|
|
12723
13028
|
for (const layer2 of layerDocs) {
|
|
12724
|
-
const
|
|
12725
|
-
if (
|
|
12726
|
-
addNode({ id: layer2.id, type: layer2.type, label: layer2.label, path:
|
|
13029
|
+
const path52 = layer2.files.map((f3) => `${KNOWLEDGE22}/${f3}`).find((rel) => exists2(join3(dir, rel)));
|
|
13030
|
+
if (path52) {
|
|
13031
|
+
addNode({ id: layer2.id, type: layer2.type, label: layer2.label, path: path52 });
|
|
12727
13032
|
presentLayers.push(layer2.id);
|
|
12728
13033
|
}
|
|
12729
13034
|
}
|
|
@@ -12763,6 +13068,24 @@ function buildGraph2(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
|
|
|
12763
13068
|
if (cand.materializedAs) addEdge(candId, `wi:${cand.materializedAs}`, "materialized_as");
|
|
12764
13069
|
}
|
|
12765
13070
|
}
|
|
13071
|
+
const resourceTitleById = /* @__PURE__ */ new Map();
|
|
13072
|
+
for (const r2 of getResources2(dir)) {
|
|
13073
|
+
resourceTitleById.set(r2.id, r2.title);
|
|
13074
|
+
const resId = `resource:${r2.id}`;
|
|
13075
|
+
addNode({
|
|
13076
|
+
id: resId,
|
|
13077
|
+
type: "project-resource",
|
|
13078
|
+
label: r2.title ? `${r2.id} ${r2.title}`.trim() : r2.id,
|
|
13079
|
+
path: r2.path
|
|
13080
|
+
});
|
|
13081
|
+
const depModules = new Set(r2.modules);
|
|
13082
|
+
if (r2.scope?.type === "module" && r2.scope.module) depModules.add(r2.scope.module);
|
|
13083
|
+
for (const mod of depModules) {
|
|
13084
|
+
const modId = `module:${mod}`;
|
|
13085
|
+
addNode({ id: modId, type: "module", label: mod });
|
|
13086
|
+
addEdge(modId, resId, "depends_on");
|
|
13087
|
+
}
|
|
13088
|
+
}
|
|
12766
13089
|
for (const wi of selectedWIs) {
|
|
12767
13090
|
const id = wi.id || wi.title;
|
|
12768
13091
|
if (!id || !id.trim()) continue;
|
|
@@ -12787,6 +13110,12 @@ function buildGraph2(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
|
|
|
12787
13110
|
addNode({ id: capId, type: "capability", label: cap });
|
|
12788
13111
|
addEdge(wiNodeId, capId, "implements");
|
|
12789
13112
|
}
|
|
13113
|
+
for (const rel of parseWorkItemResources2(wi.rawFrontmatter)) {
|
|
13114
|
+
const resId = `resource:${rel.id}`;
|
|
13115
|
+
const title = resourceTitleById.get(rel.id);
|
|
13116
|
+
addNode({ id: resId, type: "project-resource", label: title ? `${rel.id} ${title}`.trim() : rel.id });
|
|
13117
|
+
addEdge(wiNodeId, resId, RESOURCE_ROLE_EDGE2[rel.role] ?? "uses");
|
|
13118
|
+
}
|
|
12790
13119
|
for (const dec of wi.decisions) {
|
|
12791
13120
|
if (!dec || !dec.trim()) continue;
|
|
12792
13121
|
const adrId = `adr:${dec}`;
|
|
@@ -13280,8 +13609,13 @@ dependencies.
|
|
|
13280
13609
|
flags, content/copy, authentication/authorization, notifications, analytics, documentation,
|
|
13281
13610
|
operations/release \u2014 as affected, reviewed-not-affected, unknown, or not-applicable.
|
|
13282
13611
|
4. **Module review** \u2014 for multirepo, evaluate each mapped module with the same statuses.
|
|
13283
|
-
5. **
|
|
13284
|
-
|
|
13612
|
+
5. **Resource review** \u2014 ask: does this change need to observe, modify, validate or deliver against a
|
|
13613
|
+
known external resource of the project (a Project Resource under \`knowledge/tech/resources/\` \u2014
|
|
13614
|
+
database, cloud, API, queue, storage\u2026)? If so, declare it in the WI front matter \`resources:\` with
|
|
13615
|
+
a role (\`affected\`, \`implementation\`, \`validation\`, \`delivery\`). Resources are external systems,
|
|
13616
|
+
distinct from \`affected_modules\` (code). Not every WI needs resources (e.g. a CSS change does not).
|
|
13617
|
+
6. **Completeness review** \u2014 confirm: outcome covered, journey covered, modules assessed, resources
|
|
13618
|
+
assessed, unknowns visible, acceptance criteria end-to-end, scope and out-of-scope coherent.
|
|
13285
13619
|
|
|
13286
13620
|
## Rules
|
|
13287
13621
|
|
|
@@ -13292,6 +13626,8 @@ dependencies.
|
|
|
13292
13626
|
- Include at least one end-to-end acceptance criterion for user-facing changes.
|
|
13293
13627
|
- Do not reduce a product intent to the first technical implementation found.
|
|
13294
13628
|
- Evaluate surfaces and modules before proposing files.
|
|
13629
|
+
- Consider Project Resources when the scope justifies it; never invent resources not backed by
|
|
13630
|
+
project knowledge, and keep \`resources\` (external systems) separate from \`affected_modules\` (code).
|
|
13295
13631
|
- This skill refines scope and acceptance criteria but does not approve implementation readiness.
|
|
13296
13632
|
Readiness requires human confirmation through \`kaddo ready\` or the MCP \`mark_work_item_ready\` action.
|
|
13297
13633
|
|
|
@@ -14436,6 +14772,16 @@ supporting notes as \`knowledge/tech/discovery/architecture-notes.md\` and decis
|
|
|
14436
14772
|
under \`knowledge/tech/decisions/\`. Kaddo still reads the legacy root locations for backward
|
|
14437
14773
|
compatibility, but new output should use \`discovery/\`.
|
|
14438
14774
|
|
|
14775
|
+
## Project Resource candidates
|
|
14776
|
+
|
|
14777
|
+
When the knowledge describes an external system the project depends on (a database, cloud account,
|
|
14778
|
+
API, queue, storage, service\u2026), you may propose it as a **candidate** Project Resource \u2014 never create
|
|
14779
|
+
it silently. Discovery \u2260 creation: present candidates for human review and materialize only through
|
|
14780
|
+
\`kaddo_create_resource\` / \`kaddo resources create\` with explicit confirmation. Ground every
|
|
14781
|
+
candidate in the knowledge (no guessing from ambiguous names). Model the stable system, not its access
|
|
14782
|
+
interface (CLI/MCP/SQL/API are interfaces); keep \`resources\` separate from \`affected_modules\`; and
|
|
14783
|
+
never include secret values \u2014 only reference names.
|
|
14784
|
+
|
|
14439
14785
|
## Quality Checklist
|
|
14440
14786
|
|
|
14441
14787
|
- Every component is backed by evidence from the context pack.
|
|
@@ -16979,6 +17325,8 @@ function getWorkItem(dir, workItemId) {
|
|
|
16979
17325
|
path: match.relPath,
|
|
16980
17326
|
// Faithful source of truth: the artifact body already read above, not reconstructed (WI-025).
|
|
16981
17327
|
markdownBody: body.trim(),
|
|
17328
|
+
// External systems this WI relates to (WI-030), separate from affectedModules (code).
|
|
17329
|
+
resources: parseWorkItemResources2(fm),
|
|
16982
17330
|
...parseSystemImpact(dir, fm)
|
|
16983
17331
|
};
|
|
16984
17332
|
return { ...detail, refinement: computeRefinementStatus(detail) };
|
|
@@ -16987,8 +17335,8 @@ function parseGraphReason(v) {
|
|
|
16987
17335
|
if (!v || typeof v !== "object") return null;
|
|
16988
17336
|
const o2 = v;
|
|
16989
17337
|
const relationship = typeof o2.relationship === "string" && o2.relationship.trim() ? o2.relationship.trim() : null;
|
|
16990
|
-
const
|
|
16991
|
-
return relationship ||
|
|
17338
|
+
const path52 = Array.isArray(o2.path) ? o2.path.map(String).filter(Boolean) : [];
|
|
17339
|
+
return relationship || path52.length ? { relationship, path: path52 } : null;
|
|
16992
17340
|
}
|
|
16993
17341
|
function parseExplain(o2) {
|
|
16994
17342
|
const refs = Array.isArray(o2.evidence) ? o2.evidence : Array.isArray(o2.evidence_refs) ? o2.evidence_refs : [];
|
|
@@ -17043,7 +17391,7 @@ function parseOriginalSnapshot(fm) {
|
|
|
17043
17391
|
function readBody(filePath) {
|
|
17044
17392
|
try {
|
|
17045
17393
|
const raw = readFile2(filePath);
|
|
17046
|
-
return
|
|
17394
|
+
return matter92(raw).content;
|
|
17047
17395
|
} catch {
|
|
17048
17396
|
return "";
|
|
17049
17397
|
}
|
|
@@ -17245,7 +17593,7 @@ function nextWorkItemId(dir) {
|
|
|
17245
17593
|
let max = 0;
|
|
17246
17594
|
const walk = (d3) => {
|
|
17247
17595
|
if (!exists2(d3)) return;
|
|
17248
|
-
for (const entry of
|
|
17596
|
+
for (const entry of fs4.readdirSync(d3)) {
|
|
17249
17597
|
const full = join3(d3, entry);
|
|
17250
17598
|
if (isFile2(full)) {
|
|
17251
17599
|
const m2 = entry.match(/WI-(\d+)/);
|
|
@@ -17258,15 +17606,15 @@ function nextWorkItemId(dir) {
|
|
|
17258
17606
|
walk(wiDir);
|
|
17259
17607
|
return `WI-${String(max + 1).padStart(3, "0")}`;
|
|
17260
17608
|
}
|
|
17261
|
-
function
|
|
17262
|
-
|
|
17609
|
+
function atomicWrite3(filePath, content) {
|
|
17610
|
+
fs4.mkdirSync(path4.dirname(filePath), { recursive: true });
|
|
17263
17611
|
const tmp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
17264
|
-
|
|
17612
|
+
fs4.writeFileSync(tmp, content, "utf-8");
|
|
17265
17613
|
try {
|
|
17266
|
-
|
|
17614
|
+
fs4.renameSync(tmp, filePath);
|
|
17267
17615
|
} catch (err) {
|
|
17268
17616
|
try {
|
|
17269
|
-
|
|
17617
|
+
fs4.rmSync(tmp, { force: true });
|
|
17270
17618
|
} catch {
|
|
17271
17619
|
}
|
|
17272
17620
|
throw err;
|
|
@@ -17327,8 +17675,8 @@ function ensureH1(body, title) {
|
|
|
17327
17675
|
|
|
17328
17676
|
${trimmed}`;
|
|
17329
17677
|
}
|
|
17330
|
-
function
|
|
17331
|
-
return
|
|
17678
|
+
function serialize2(data, body) {
|
|
17679
|
+
return matter102.stringify(`
|
|
17332
17680
|
${body.trim()}
|
|
17333
17681
|
`, data);
|
|
17334
17682
|
}
|
|
@@ -17396,8 +17744,8 @@ function createWorkItem(dir, opts) {
|
|
|
17396
17744
|
const relPath = `${WORK_ITEMS_DIR}/draft/${id}-${slugify22(title)}.md`;
|
|
17397
17745
|
const filePath = join3(dir, relPath);
|
|
17398
17746
|
if (exists2(filePath)) throw new WorkItemWriteError("INVALID_INPUT", `Work Item file already exists: ${relPath}`);
|
|
17399
|
-
const raw =
|
|
17400
|
-
|
|
17747
|
+
const raw = serialize2(data, body);
|
|
17748
|
+
atomicWrite3(filePath, raw);
|
|
17401
17749
|
return { id, path: relPath, revision: revisionOf(raw) };
|
|
17402
17750
|
}
|
|
17403
17751
|
function dimensionOf(type) {
|
|
@@ -17722,6 +18070,7 @@ function buildImplementationHandoff(dir, workItemId) {
|
|
|
17722
18070
|
const affectedModules = match.affectedModules;
|
|
17723
18071
|
const domains = match.domains;
|
|
17724
18072
|
const scopeConfidence = match.scopeConfidence;
|
|
18073
|
+
const wiResources = parseWorkItemResources2(match.rawFrontmatter);
|
|
17725
18074
|
const lines = [
|
|
17726
18075
|
`Implement Work Item ${wiId} \u2014 "${wiTitle}" \u2014 in project "${projectName}" using Kaddo.`,
|
|
17727
18076
|
"",
|
|
@@ -17811,6 +18160,41 @@ function buildImplementationHandoff(dir, workItemId) {
|
|
|
17811
18160
|
"Do not copy all legacy knowledge \u2014 assemble proportionally to scope."
|
|
17812
18161
|
);
|
|
17813
18162
|
}
|
|
18163
|
+
if (wiResources.length > 0) {
|
|
18164
|
+
lines.push(
|
|
18165
|
+
"",
|
|
18166
|
+
"--- Relevant Resources ---",
|
|
18167
|
+
"",
|
|
18168
|
+
"This Work Item relates to external project resources. The handoff INFORMS you about them \u2014 it",
|
|
18169
|
+
"never connects to them, runs any interface, or reveals credential values. Respect each boundary."
|
|
18170
|
+
);
|
|
18171
|
+
for (const rel of wiResources) {
|
|
18172
|
+
const res = getResource2(dir, rel.id);
|
|
18173
|
+
if (!res) {
|
|
18174
|
+
lines.push("", `- ${rel.id} (role: ${rel.role}) \u2014 definition not found under knowledge/tech/resources/.`);
|
|
18175
|
+
continue;
|
|
18176
|
+
}
|
|
18177
|
+
lines.push("", `- ${res.id} \u2014 ${res.title} (role: ${rel.role})`);
|
|
18178
|
+
if (res.resourceType || res.provider) {
|
|
18179
|
+
lines.push(` type: ${res.resourceType ?? "\u2014"}${res.provider ? ` \xB7 provider: ${res.provider}` : ""}`);
|
|
18180
|
+
}
|
|
18181
|
+
if (res.purpose) lines.push(` purpose: ${res.purpose}`);
|
|
18182
|
+
if (res.environments.length > 0) lines.push(` environments: ${res.environments.join(", ")}`);
|
|
18183
|
+
if (res.interfaces.length > 0) {
|
|
18184
|
+
const ifaces = res.interfaces.map((i) => i.tool || i.provider ? `${i.type} (${i.tool ?? i.provider})` : i.type);
|
|
18185
|
+
lines.push(` interfaces: ${ifaces.join(", ")}`);
|
|
18186
|
+
}
|
|
18187
|
+
for (const [env, ops] of Object.entries(res.boundaries)) {
|
|
18188
|
+
lines.push(` boundary (${env}): ${ops.join(", ")}`);
|
|
18189
|
+
}
|
|
18190
|
+
if (res.authRefs.length > 0) lines.push(` auth references (names only): ${res.authRefs.join(", ")}`);
|
|
18191
|
+
}
|
|
18192
|
+
lines.push(
|
|
18193
|
+
"",
|
|
18194
|
+
"Treat these as knowledge, not availability: an interface being declared does not mean it is",
|
|
18195
|
+
"installed or connected. Do not execute any interface or migration without explicit human confirmation."
|
|
18196
|
+
);
|
|
18197
|
+
}
|
|
17814
18198
|
lines.push(
|
|
17815
18199
|
"",
|
|
17816
18200
|
"--- Implementation Plan ---",
|
|
@@ -17831,6 +18215,7 @@ function buildImplementationHandoff(dir, workItemId) {
|
|
|
17831
18215
|
projectName,
|
|
17832
18216
|
lifecycle,
|
|
17833
18217
|
affectedModules,
|
|
18218
|
+
resources: wiResources,
|
|
17834
18219
|
domains,
|
|
17835
18220
|
scopeConfidence,
|
|
17836
18221
|
hasUnmaterializedDecisions: unmaterialized,
|
|
@@ -18082,7 +18467,7 @@ function detectFormat(raw) {
|
|
|
18082
18467
|
if (!trimmed) throw new ImportError("EMPTY_INPUT", "Import content is empty.");
|
|
18083
18468
|
if (trimmed.startsWith("---")) {
|
|
18084
18469
|
try {
|
|
18085
|
-
const { data } =
|
|
18470
|
+
const { data } = matter112(trimmed);
|
|
18086
18471
|
if (data && typeof data === "object" && Object.keys(data).length > 0) {
|
|
18087
18472
|
const d3 = data;
|
|
18088
18473
|
if (d3.type === "work-item" || d3.id && typeof d3.id === "string" && /^WI-\d+/.test(d3.id)) {
|
|
@@ -18104,7 +18489,7 @@ function parseContent(raw) {
|
|
|
18104
18489
|
let bodyText = trimmed;
|
|
18105
18490
|
if (format === "kaddo-frontmatter" || format === "markdown-frontmatter") {
|
|
18106
18491
|
try {
|
|
18107
|
-
const parsed =
|
|
18492
|
+
const parsed = matter112(trimmed);
|
|
18108
18493
|
frontmatterFields = parsed.data ?? {};
|
|
18109
18494
|
bodyText = parsed.content.trim();
|
|
18110
18495
|
} catch {
|
|
@@ -18201,7 +18586,7 @@ function findDuplicateByHash(dir, hash) {
|
|
|
18201
18586
|
const raw = readFile2(item.filePath);
|
|
18202
18587
|
if (!raw) continue;
|
|
18203
18588
|
try {
|
|
18204
|
-
const { data } =
|
|
18589
|
+
const { data } = matter112(raw);
|
|
18205
18590
|
const source = data?.source;
|
|
18206
18591
|
if (source && typeof source === "object" && !Array.isArray(source)) {
|
|
18207
18592
|
const s = source;
|
|
@@ -18268,7 +18653,7 @@ function importWorkItem(dir, opts) {
|
|
|
18268
18653
|
};
|
|
18269
18654
|
}
|
|
18270
18655
|
if (opts.onConflict === "replace") {
|
|
18271
|
-
|
|
18656
|
+
fs5.rmSync(existing.filePath, { force: true });
|
|
18272
18657
|
replacedWorkItem = existing.workItemId;
|
|
18273
18658
|
}
|
|
18274
18659
|
}
|
|
@@ -18710,8 +19095,8 @@ async function handleJiraResponse(res) {
|
|
|
18710
19095
|
}
|
|
18711
19096
|
return await res.json();
|
|
18712
19097
|
}
|
|
18713
|
-
async function jiraFetch(baseUrl,
|
|
18714
|
-
const url = new URL(
|
|
19098
|
+
async function jiraFetch(baseUrl, path52, auth, timeoutMs, params) {
|
|
19099
|
+
const url = new URL(path52, baseUrl + "/");
|
|
18715
19100
|
if (params) for (const [k2, v] of Object.entries(params)) url.searchParams.set(k2, v);
|
|
18716
19101
|
const controller = new AbortController();
|
|
18717
19102
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
@@ -18729,8 +19114,8 @@ async function jiraFetch(baseUrl, path42, auth, timeoutMs, params) {
|
|
|
18729
19114
|
clearTimeout(timer);
|
|
18730
19115
|
}
|
|
18731
19116
|
}
|
|
18732
|
-
async function jiraPost(baseUrl,
|
|
18733
|
-
const url = new URL(
|
|
19117
|
+
async function jiraPost(baseUrl, path52, auth, timeoutMs, body) {
|
|
19118
|
+
const url = new URL(path52, baseUrl + "/");
|
|
18734
19119
|
const controller = new AbortController();
|
|
18735
19120
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
18736
19121
|
try {
|
|
@@ -19096,7 +19481,7 @@ function buildInitiativeContent(id, input) {
|
|
|
19096
19481
|
if (input.priority) data.priority = input.priority;
|
|
19097
19482
|
if (input.source) data.source = input.source;
|
|
19098
19483
|
if (input.sourceId) data.source_id = input.sourceId;
|
|
19099
|
-
return
|
|
19484
|
+
return matter13.stringify("\n" + (input.body ?? defaultBody(input.title.trim())), data);
|
|
19100
19485
|
}
|
|
19101
19486
|
function createInitiative(dir, input) {
|
|
19102
19487
|
if (!input.title?.trim()) throw new InitiativeWriteError("Initiative title is required.");
|
|
@@ -19120,10 +19505,10 @@ function transitionInitiative(dir, id, to) {
|
|
|
19120
19505
|
throw new InitiativeWriteError(`Invalid transition ${ini.status} \u2192 ${to}.`);
|
|
19121
19506
|
}
|
|
19122
19507
|
const raw = readFile2(ini.filePath);
|
|
19123
|
-
const { data, content } =
|
|
19508
|
+
const { data, content } = matter13(raw, {});
|
|
19124
19509
|
data.status = to;
|
|
19125
19510
|
if (to === "completed") data.completed_at = today2();
|
|
19126
|
-
const newContent =
|
|
19511
|
+
const newContent = matter13.stringify(content, data);
|
|
19127
19512
|
writeFile2(ini.filePath, newContent);
|
|
19128
19513
|
const updated = parseInitiative2(dir, ini.filePath, newContent);
|
|
19129
19514
|
if (!updated) throw new InitiativeWriteError("Failed to parse the Initiative after transition.");
|
|
@@ -19133,7 +19518,7 @@ function addCandidate(dir, id, candidate) {
|
|
|
19133
19518
|
const ini = getInitiative(dir, id);
|
|
19134
19519
|
if (!ini) throw new InitiativeWriteError(`Initiative ${id} not found.`);
|
|
19135
19520
|
const raw = readFile2(ini.filePath);
|
|
19136
|
-
const { data, content } =
|
|
19521
|
+
const { data, content } = matter13(raw, {});
|
|
19137
19522
|
const existing = Array.isArray(data.candidates) ? data.candidates : [];
|
|
19138
19523
|
const fallbackId = `WI-CANDIDATE-${String(existing.length + 1).padStart(3, "0")}`;
|
|
19139
19524
|
const normalized = normalizeCandidate(candidate, fallbackId);
|
|
@@ -19141,7 +19526,7 @@ function addCandidate(dir, id, candidate) {
|
|
|
19141
19526
|
throw new InitiativeWriteError(`Candidate ${normalized.id} already exists in ${id}.`);
|
|
19142
19527
|
}
|
|
19143
19528
|
data.candidates = [...existing, candidateToData(normalized)];
|
|
19144
|
-
const newContent =
|
|
19529
|
+
const newContent = matter13.stringify(content, data);
|
|
19145
19530
|
writeFile2(ini.filePath, newContent);
|
|
19146
19531
|
const updated = parseInitiative2(dir, ini.filePath, newContent);
|
|
19147
19532
|
if (!updated) throw new InitiativeWriteError("Failed to parse the Initiative after adding a candidate.");
|
|
@@ -19151,7 +19536,7 @@ function updateInitiative(dir, id, patch) {
|
|
|
19151
19536
|
const ini = getInitiative(dir, id);
|
|
19152
19537
|
if (!ini) throw new InitiativeWriteError(`Initiative ${id} not found.`);
|
|
19153
19538
|
const raw = readFile2(ini.filePath);
|
|
19154
|
-
const { data, content } =
|
|
19539
|
+
const { data, content } = matter13(raw, {});
|
|
19155
19540
|
if (patch.title !== void 0) {
|
|
19156
19541
|
if (!patch.title.trim()) throw new InitiativeWriteError("Initiative title cannot be empty.");
|
|
19157
19542
|
data.title = patch.title.trim();
|
|
@@ -19161,7 +19546,7 @@ function updateInitiative(dir, id, patch) {
|
|
|
19161
19546
|
if (patch.knowledgeLevel !== void 0) data.knowledge_level = patch.knowledgeLevel;
|
|
19162
19547
|
if (patch.domains !== void 0) data.domains = patch.domains;
|
|
19163
19548
|
if (patch.relatedCapabilities !== void 0) data.related_capabilities = patch.relatedCapabilities;
|
|
19164
|
-
const newContent =
|
|
19549
|
+
const newContent = matter13.stringify(content, data);
|
|
19165
19550
|
writeFile2(ini.filePath, newContent);
|
|
19166
19551
|
const updated = parseInitiative2(dir, ini.filePath, newContent);
|
|
19167
19552
|
if (!updated) throw new InitiativeWriteError("Failed to parse the Initiative after update.");
|
|
@@ -19179,10 +19564,10 @@ function addExternalLink(dir, id, link) {
|
|
|
19179
19564
|
throw new InitiativeWriteError(`External link ${link.integration}:${link.externalId} already exists on ${id}.`);
|
|
19180
19565
|
}
|
|
19181
19566
|
const raw = readFile2(ini.filePath);
|
|
19182
|
-
const { data, content } =
|
|
19567
|
+
const { data, content } = matter13(raw, {});
|
|
19183
19568
|
const existing = Array.isArray(data.external_links) ? data.external_links : [];
|
|
19184
19569
|
data.external_links = [...existing, externalLinkToData(link)];
|
|
19185
|
-
const newContent =
|
|
19570
|
+
const newContent = matter13.stringify(content, data);
|
|
19186
19571
|
writeFile2(ini.filePath, newContent);
|
|
19187
19572
|
const updated = parseInitiative2(dir, ini.filePath, newContent);
|
|
19188
19573
|
if (!updated) throw new InitiativeWriteError("Failed to parse the Initiative after adding an external link.");
|
|
@@ -19251,7 +19636,7 @@ function buildWorkItemContent(wiId, iniId, candidate) {
|
|
|
19251
19636
|
"_What did we learn? Update after completion._",
|
|
19252
19637
|
""
|
|
19253
19638
|
].join("\n");
|
|
19254
|
-
return
|
|
19639
|
+
return matter13.stringify("\n" + body, data);
|
|
19255
19640
|
}
|
|
19256
19641
|
function materializeCandidate(dir, id, candidateId) {
|
|
19257
19642
|
const ini = getInitiative(dir, id);
|
|
@@ -19268,13 +19653,13 @@ function materializeCandidate(dir, id, candidateId) {
|
|
|
19268
19653
|
ensureDir2(join3(dir, DRAFT_DIR));
|
|
19269
19654
|
writeFile2(join3(dir, DRAFT_DIR, fileName), buildWorkItemContent(wiId, ini.id, candidate));
|
|
19270
19655
|
const raw = readFile2(ini.filePath);
|
|
19271
|
-
const { data, content } =
|
|
19656
|
+
const { data, content } = matter13(raw, {});
|
|
19272
19657
|
const cands = Array.isArray(data.candidates) ? data.candidates : [];
|
|
19273
19658
|
for (const c of cands) {
|
|
19274
19659
|
if (c && typeof c === "object" && String(c.id) === candidateId) c.materialized_as = wiId;
|
|
19275
19660
|
}
|
|
19276
19661
|
data.candidates = cands;
|
|
19277
|
-
const newContent =
|
|
19662
|
+
const newContent = matter13.stringify(content, data);
|
|
19278
19663
|
writeFile2(ini.filePath, newContent);
|
|
19279
19664
|
const updated = parseInitiative2(dir, ini.filePath, newContent);
|
|
19280
19665
|
if (!updated) throw new InitiativeWriteError("Failed to parse the Initiative after materialization.");
|
|
@@ -19385,6 +19770,164 @@ function evaluateInitiativeCompletion(dir, ini) {
|
|
|
19385
19770
|
};
|
|
19386
19771
|
}
|
|
19387
19772
|
|
|
19773
|
+
// ../cli/src/core/resource-write.ts
|
|
19774
|
+
import fs6 from "fs";
|
|
19775
|
+
import path5 from "path";
|
|
19776
|
+
import matter14 from "gray-matter";
|
|
19777
|
+
var RESOURCES_DIR = "knowledge/tech/resources";
|
|
19778
|
+
var ResourceWriteError = class extends Error {
|
|
19779
|
+
constructor(code, message) {
|
|
19780
|
+
super(message);
|
|
19781
|
+
this.code = code;
|
|
19782
|
+
this.name = "ResourceWriteError";
|
|
19783
|
+
}
|
|
19784
|
+
code;
|
|
19785
|
+
};
|
|
19786
|
+
function slug4(s) {
|
|
19787
|
+
return s.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60);
|
|
19788
|
+
}
|
|
19789
|
+
function atomicWrite(filePath, content) {
|
|
19790
|
+
const tmp = `${filePath}.tmp-${process.pid}-${Date.now()}`;
|
|
19791
|
+
fs6.writeFileSync(tmp, content, "utf-8");
|
|
19792
|
+
fs6.renameSync(tmp, filePath);
|
|
19793
|
+
}
|
|
19794
|
+
function sanitizeRefs(refs) {
|
|
19795
|
+
if (!Array.isArray(refs)) return [];
|
|
19796
|
+
return refs.map((r2) => String(r2).trim()).filter((r2) => r2 && !r2.includes("=") && /^[A-Za-z0-9_.-]+$/.test(r2));
|
|
19797
|
+
}
|
|
19798
|
+
function allResourceArtifacts(dir) {
|
|
19799
|
+
return discoverKnowledge(dir).filter((a2) => a2.type === RESOURCE_ARTIFACT_TYPE);
|
|
19800
|
+
}
|
|
19801
|
+
function nextResourceId(dir, title, explicit) {
|
|
19802
|
+
const existing = new Set(allResourceArtifacts(dir).map((a2) => (a2.id || "").toUpperCase()));
|
|
19803
|
+
if (explicit && explicit.trim()) return explicit.trim();
|
|
19804
|
+
const base = `RES-${slug4(title) || "resource"}`;
|
|
19805
|
+
if (!existing.has(base.toUpperCase())) return base;
|
|
19806
|
+
for (let i = 2; i < 1e3; i++) {
|
|
19807
|
+
const candidate = `${base}-${i}`;
|
|
19808
|
+
if (!existing.has(candidate.toUpperCase())) return candidate;
|
|
19809
|
+
}
|
|
19810
|
+
throw new ResourceWriteError("ID_EXHAUSTED", `Could not derive a unique id for "${title}".`);
|
|
19811
|
+
}
|
|
19812
|
+
function validateResource(dir, input) {
|
|
19813
|
+
const findings = [];
|
|
19814
|
+
if (input.resourceType && !RESOURCE_TYPES.includes(input.resourceType)) {
|
|
19815
|
+
findings.push({ level: "warning", message: `Unknown resource_type "${input.resourceType}" (not in the known set).` });
|
|
19816
|
+
}
|
|
19817
|
+
for (const i of input.accessInterfaces ?? []) {
|
|
19818
|
+
if (i.type && !INTERFACE_TYPES.includes(i.type)) {
|
|
19819
|
+
findings.push({ level: "warning", message: `Unknown access interface type "${i.type}".` });
|
|
19820
|
+
}
|
|
19821
|
+
}
|
|
19822
|
+
const mapped = loadMappedModules(dir).map((m2) => m2.id);
|
|
19823
|
+
if (mapped.length > 0) {
|
|
19824
|
+
const refs = /* @__PURE__ */ new Set();
|
|
19825
|
+
if (input.scope?.type === "module" && input.scope.module) refs.add(input.scope.module);
|
|
19826
|
+
for (const m2 of input.modules ?? []) refs.add(m2);
|
|
19827
|
+
for (const m2 of refs) {
|
|
19828
|
+
if (!mapped.includes(m2)) findings.push({ level: "warning", message: `Module "${m2}" is not in .kaddo/modules.yml.` });
|
|
19829
|
+
}
|
|
19830
|
+
}
|
|
19831
|
+
if (input.scope && input.scope.type === "module" && !input.scope.module) {
|
|
19832
|
+
findings.push({ level: "error", message: 'scope.type "module" requires a module id.' });
|
|
19833
|
+
}
|
|
19834
|
+
return findings;
|
|
19835
|
+
}
|
|
19836
|
+
function buildFrontmatter(id, input, base = {}) {
|
|
19837
|
+
const data = { ...base };
|
|
19838
|
+
data.type = RESOURCE_ARTIFACT_TYPE;
|
|
19839
|
+
data.id = id;
|
|
19840
|
+
if (input.title != null) data.title = input.title;
|
|
19841
|
+
if (input.resourceType != null) data.resource_type = input.resourceType;
|
|
19842
|
+
if (input.provider != null) data.provider = input.provider;
|
|
19843
|
+
if (input.environments != null) data.environments = input.environments;
|
|
19844
|
+
if (input.scope != null) data.scope = input.scope.module ? { type: input.scope.type, module: input.scope.module } : { type: input.scope.type };
|
|
19845
|
+
if (input.modules != null) data.modules = input.modules;
|
|
19846
|
+
if (input.accessInterfaces != null) data.access_interfaces = input.accessInterfaces;
|
|
19847
|
+
if (input.accessBoundaries != null) data.access_boundaries = input.accessBoundaries;
|
|
19848
|
+
if (input.authentication != null) {
|
|
19849
|
+
const mode = input.authentication.mode;
|
|
19850
|
+
const refs = sanitizeRefs(input.authentication.refs);
|
|
19851
|
+
data.authentication = { ...mode ? { mode } : {}, ...refs.length ? { refs } : {} };
|
|
19852
|
+
}
|
|
19853
|
+
return data;
|
|
19854
|
+
}
|
|
19855
|
+
function serialize(data, body) {
|
|
19856
|
+
return matter14.stringify(`
|
|
19857
|
+
${body.trim()}
|
|
19858
|
+
`, data);
|
|
19859
|
+
}
|
|
19860
|
+
function createResource(dir, input) {
|
|
19861
|
+
const title = (input.title ?? "").trim();
|
|
19862
|
+
if (!title) throw new ResourceWriteError("INVALID_INPUT", "A resource title is required.");
|
|
19863
|
+
const resourceType = (input.resourceType ?? "").trim();
|
|
19864
|
+
if (!resourceType) throw new ResourceWriteError("INVALID_INPUT", "A resource_type is required.");
|
|
19865
|
+
const findings = validateResource(dir, input);
|
|
19866
|
+
if (findings.some((f3) => f3.level === "error")) {
|
|
19867
|
+
throw new ResourceWriteError("VALIDATION_FAILED", findings.filter((f3) => f3.level === "error").map((f3) => f3.message).join("; "));
|
|
19868
|
+
}
|
|
19869
|
+
const existing = allResourceArtifacts(dir);
|
|
19870
|
+
if (input.id && existing.some((a2) => (a2.id || "").toUpperCase() === input.id.toUpperCase())) {
|
|
19871
|
+
throw new ResourceWriteError("DUPLICATE", `A resource with id "${input.id}" already exists.`);
|
|
19872
|
+
}
|
|
19873
|
+
if (existing.some((a2) => (a2.title || "").toLowerCase() === title.toLowerCase())) {
|
|
19874
|
+
throw new ResourceWriteError("DUPLICATE", `A resource titled "${title}" already exists.`);
|
|
19875
|
+
}
|
|
19876
|
+
const id = nextResourceId(dir, title, input.id);
|
|
19877
|
+
const data = buildFrontmatter(id, input);
|
|
19878
|
+
const body = `# Purpose
|
|
19879
|
+
|
|
19880
|
+
${(input.purpose ?? "").trim() || "TODO: describe what this resource is for."}`;
|
|
19881
|
+
const relPath = `${RESOURCES_DIR}/${slug4(title) || slug4(id)}.md`;
|
|
19882
|
+
const filePath = join(dir, relPath);
|
|
19883
|
+
if (exists(filePath)) throw new ResourceWriteError("DUPLICATE", `Resource file already exists: ${relPath}`);
|
|
19884
|
+
ensureDir(path5.dirname(filePath));
|
|
19885
|
+
atomicWrite(filePath, serialize(data, body));
|
|
19886
|
+
return { id, path: relPath, findings };
|
|
19887
|
+
}
|
|
19888
|
+
function updateResource(dir, id, input) {
|
|
19889
|
+
const match = allResourceArtifacts(dir).find((a2) => a2.id === id || a2.title === id);
|
|
19890
|
+
if (!match) throw new ResourceWriteError("NOT_FOUND", `Project Resource "${id}" not found.`);
|
|
19891
|
+
const findings = validateResource(dir, input);
|
|
19892
|
+
if (findings.some((f3) => f3.level === "error")) {
|
|
19893
|
+
throw new ResourceWriteError("VALIDATION_FAILED", findings.filter((f3) => f3.level === "error").map((f3) => f3.message).join("; "));
|
|
19894
|
+
}
|
|
19895
|
+
const raw = readFile(match.filePath);
|
|
19896
|
+
const parsed = matter14(raw);
|
|
19897
|
+
const base = parsed.data ?? {};
|
|
19898
|
+
const data = buildFrontmatter(match.id || id, input, base);
|
|
19899
|
+
let body = parsed.content.trim();
|
|
19900
|
+
if (input.purpose != null) {
|
|
19901
|
+
const purpose = `# Purpose
|
|
19902
|
+
|
|
19903
|
+
${input.purpose.trim()}`;
|
|
19904
|
+
body = /^#\s+Purpose/m.test(body) ? body.replace(/#\s+Purpose[\s\S]*?(?=\n#\s|\n*$)/, purpose + "\n") : `${purpose}
|
|
19905
|
+
|
|
19906
|
+
${body}`.trim();
|
|
19907
|
+
}
|
|
19908
|
+
atomicWrite(match.filePath, serialize(data, body));
|
|
19909
|
+
return { id: match.id || id, path: match.relPath, findings };
|
|
19910
|
+
}
|
|
19911
|
+
function getResourceReferences(dir, id) {
|
|
19912
|
+
const refs = [];
|
|
19913
|
+
for (const wi of discoverWorkItems(dir)) {
|
|
19914
|
+
for (const rel of parseWorkItemResources(wi.rawFrontmatter)) {
|
|
19915
|
+
if (rel.id === id) refs.push({ kind: "work-item", id: wi.id || wi.title, role: rel.role, path: wi.relPath });
|
|
19916
|
+
}
|
|
19917
|
+
}
|
|
19918
|
+
return refs;
|
|
19919
|
+
}
|
|
19920
|
+
function deleteResource(dir, id, opts = {}) {
|
|
19921
|
+
const match = allResourceArtifacts(dir).find((a2) => a2.id === id || a2.title === id);
|
|
19922
|
+
if (!match) throw new ResourceWriteError("NOT_FOUND", `Project Resource "${id}" not found.`);
|
|
19923
|
+
const references = getResourceReferences(dir, match.id || id);
|
|
19924
|
+
if (!opts.confirm) {
|
|
19925
|
+
return { deleted: false, preview: { id: match.id || id, references } };
|
|
19926
|
+
}
|
|
19927
|
+
fs6.rmSync(match.filePath, { force: true });
|
|
19928
|
+
return { deleted: true, path: match.relPath };
|
|
19929
|
+
}
|
|
19930
|
+
|
|
19388
19931
|
// src/tools.ts
|
|
19389
19932
|
var ok = (data) => ({ ok: true, data });
|
|
19390
19933
|
var fail = (message) => ({ ok: false, message });
|
|
@@ -19480,12 +20023,12 @@ function markWorkItemReady(root, id, confirm2) {
|
|
|
19480
20023
|
}
|
|
19481
20024
|
const raw = readText(root, item.path);
|
|
19482
20025
|
if (!raw) return fail(`Could not read Work Item file: ${item.path}`);
|
|
19483
|
-
const parsed =
|
|
20026
|
+
const parsed = matter15(raw);
|
|
19484
20027
|
const data = { ...parsed.data };
|
|
19485
20028
|
const today3 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
19486
20029
|
data.status = "ready";
|
|
19487
20030
|
data.ready_at = today3;
|
|
19488
|
-
const newContent =
|
|
20031
|
+
const newContent = matter15.stringify(parsed.content, data);
|
|
19489
20032
|
const posixPath = item.path.replace(/\\/g, "/");
|
|
19490
20033
|
let newRelPath = item.path;
|
|
19491
20034
|
if (posixPath.includes("/work-items/draft/")) {
|
|
@@ -19648,11 +20191,72 @@ function setTelemetryConsentTool(root, args) {
|
|
|
19648
20191
|
persistConsent(root, consent);
|
|
19649
20192
|
return ok({ applied: true, consent, message: `Telemetry ${consent}.` });
|
|
19650
20193
|
}
|
|
20194
|
+
function listResourcesTool(root) {
|
|
20195
|
+
return ok(getResources(root));
|
|
20196
|
+
}
|
|
20197
|
+
function getResourceTool(root, id) {
|
|
20198
|
+
const resource = getResource(root, id);
|
|
20199
|
+
if (!resource) return fail(`Project Resource "${id}" not found.`);
|
|
20200
|
+
return ok(resource);
|
|
20201
|
+
}
|
|
20202
|
+
function toResourceInput(args) {
|
|
20203
|
+
const input = {};
|
|
20204
|
+
if (args.title != null) input.title = args.title;
|
|
20205
|
+
if (args.resource_type != null) input.resourceType = args.resource_type;
|
|
20206
|
+
if (args.provider != null) input.provider = args.provider;
|
|
20207
|
+
if (args.environments != null) input.environments = args.environments;
|
|
20208
|
+
if (args.scope != null || args.module != null) {
|
|
20209
|
+
const type = args.scope || (args.module ? "module" : "system");
|
|
20210
|
+
input.scope = type === "module" ? { type: "module", module: args.module ?? null } : { type: "system" };
|
|
20211
|
+
}
|
|
20212
|
+
if (args.modules != null) input.modules = args.modules;
|
|
20213
|
+
if (args.purpose != null) input.purpose = args.purpose;
|
|
20214
|
+
if (args.access_interfaces != null) input.accessInterfaces = args.access_interfaces;
|
|
20215
|
+
if (args.authentication != null) input.authentication = args.authentication;
|
|
20216
|
+
return input;
|
|
20217
|
+
}
|
|
20218
|
+
function createResourceTool(root, args, confirm2) {
|
|
20219
|
+
const input = toResourceInput(args);
|
|
20220
|
+
const findings = validateResource(root, input);
|
|
20221
|
+
if (!confirm2) {
|
|
20222
|
+
return ok({ status: "needs_confirmation", action: "create", proposal: input, findings, message: "Re-call with confirm=true to create this resource." });
|
|
20223
|
+
}
|
|
20224
|
+
try {
|
|
20225
|
+
return ok({ status: "created", ...createResource(root, input) });
|
|
20226
|
+
} catch (e) {
|
|
20227
|
+
return fail(e instanceof ResourceWriteError ? e.message : String(e));
|
|
20228
|
+
}
|
|
20229
|
+
}
|
|
20230
|
+
function updateResourceTool(root, id, args, confirm2) {
|
|
20231
|
+
if (!getResource(root, id)) return fail(`Project Resource "${id}" not found.`);
|
|
20232
|
+
const input = toResourceInput(args);
|
|
20233
|
+
const findings = validateResource(root, input);
|
|
20234
|
+
if (!confirm2) {
|
|
20235
|
+
return ok({ status: "needs_confirmation", action: "update", id, changes: input, findings, message: "Re-call with confirm=true to apply this update." });
|
|
20236
|
+
}
|
|
20237
|
+
try {
|
|
20238
|
+
return ok({ status: "updated", ...updateResource(root, id, input) });
|
|
20239
|
+
} catch (e) {
|
|
20240
|
+
return fail(e instanceof ResourceWriteError ? e.message : String(e));
|
|
20241
|
+
}
|
|
20242
|
+
}
|
|
20243
|
+
function deleteResourceTool(root, id, confirm2) {
|
|
20244
|
+
if (!getResource(root, id)) return fail(`Project Resource "${id}" not found.`);
|
|
20245
|
+
if (!confirm2) {
|
|
20246
|
+
const references = getResourceReferences(root, id);
|
|
20247
|
+
return ok({ status: "needs_confirmation", action: "delete", id, references, message: "Re-call with confirm=true to delete. References in other artifacts are NOT removed." });
|
|
20248
|
+
}
|
|
20249
|
+
try {
|
|
20250
|
+
return ok({ status: "deleted", ...deleteResource(root, id, { confirm: true }) });
|
|
20251
|
+
} catch (e) {
|
|
20252
|
+
return fail(e instanceof ResourceWriteError ? e.message : String(e));
|
|
20253
|
+
}
|
|
20254
|
+
}
|
|
19651
20255
|
|
|
19652
20256
|
// src/multirepo.ts
|
|
19653
|
-
import
|
|
19654
|
-
import
|
|
19655
|
-
import
|
|
20257
|
+
import fs7 from "fs";
|
|
20258
|
+
import path6 from "path";
|
|
20259
|
+
import matter16 from "gray-matter";
|
|
19656
20260
|
import { parse as parseYaml11, stringify as stringifyYaml7 } from "yaml";
|
|
19657
20261
|
var ok2 = (data) => ({ ok: true, data });
|
|
19658
20262
|
var fail2 = (message) => ({ ok: false, message });
|
|
@@ -19672,24 +20276,24 @@ function isCore2(config) {
|
|
|
19672
20276
|
return projectRole2(config) === "core";
|
|
19673
20277
|
}
|
|
19674
20278
|
function readModuleFile(root, repoPath, relFile) {
|
|
19675
|
-
const moduleDir =
|
|
19676
|
-
const abs =
|
|
20279
|
+
const moduleDir = path6.resolve(root, repoPath);
|
|
20280
|
+
const abs = path6.resolve(moduleDir, relFile);
|
|
19677
20281
|
if (!abs.startsWith(moduleDir)) return null;
|
|
19678
|
-
if (!
|
|
19679
|
-
return
|
|
20282
|
+
if (!fs7.existsSync(abs) || !fs7.statSync(abs).isFile()) return null;
|
|
20283
|
+
return fs7.readFileSync(abs, "utf-8");
|
|
19680
20284
|
}
|
|
19681
20285
|
function detectModuleStatus2(root, repoPath) {
|
|
19682
|
-
const moduleDir =
|
|
19683
|
-
if (!
|
|
20286
|
+
const moduleDir = path6.resolve(root, repoPath);
|
|
20287
|
+
if (!fs7.existsSync(moduleDir)) {
|
|
19684
20288
|
return { status: "missing", configured: false, moduleContext: false, currentState: false, codebase: false, warning: `Directory \`${repoPath}\` does not exist.` };
|
|
19685
20289
|
}
|
|
19686
|
-
const configPath =
|
|
19687
|
-
if (!
|
|
20290
|
+
const configPath = path6.join(moduleDir, ".kaddo", "config.yml");
|
|
20291
|
+
if (!fs7.existsSync(configPath)) {
|
|
19688
20292
|
return { status: "not_configured", configured: false, moduleContext: false, currentState: false, codebase: false, warning: `Run \`kaddo init\` inside \`${repoPath}\` and select multirepo \u2192 module.` };
|
|
19689
20293
|
}
|
|
19690
20294
|
let parsed = null;
|
|
19691
20295
|
try {
|
|
19692
|
-
parsed = parseYaml11(
|
|
20296
|
+
parsed = parseYaml11(fs7.readFileSync(configPath, "utf-8"));
|
|
19693
20297
|
} catch {
|
|
19694
20298
|
}
|
|
19695
20299
|
const role = parsed?.project?.role ?? parsed?.multirepo?.role;
|
|
@@ -19700,14 +20304,14 @@ function detectModuleStatus2(root, repoPath) {
|
|
|
19700
20304
|
status: "configured",
|
|
19701
20305
|
configured: true,
|
|
19702
20306
|
role: "module",
|
|
19703
|
-
moduleContext:
|
|
19704
|
-
currentState:
|
|
19705
|
-
codebase:
|
|
20307
|
+
moduleContext: fs7.existsSync(path6.join(moduleDir, "knowledge", "tech", "module", "module-context.md")) || fs7.existsSync(path6.join(moduleDir, "knowledge", "module", "module-context.md")),
|
|
20308
|
+
currentState: fs7.existsSync(path6.join(moduleDir, "knowledge", "tech", "current-state.md")),
|
|
20309
|
+
codebase: fs7.existsSync(path6.join(moduleDir, "knowledge", "tech", "codebase.md"))
|
|
19706
20310
|
};
|
|
19707
20311
|
}
|
|
19708
20312
|
function resolveModuleContextPath(root, repoPath) {
|
|
19709
|
-
const moduleDir =
|
|
19710
|
-
if (
|
|
20313
|
+
const moduleDir = path6.resolve(root, repoPath);
|
|
20314
|
+
if (fs7.existsSync(path6.join(moduleDir, "knowledge", "tech", "module", "module-context.md"))) {
|
|
19711
20315
|
return "knowledge/tech/module/module-context.md";
|
|
19712
20316
|
}
|
|
19713
20317
|
return "knowledge/module/module-context.md";
|
|
@@ -19804,7 +20408,7 @@ function validateWorkItemModulesTool(root, args) {
|
|
|
19804
20408
|
if (!wi) return fail2(`Work Item "${args.workItemId}" not found.`);
|
|
19805
20409
|
const raw = readText(root, wi.path);
|
|
19806
20410
|
if (!raw) return fail2(`Could not read Work Item file.`);
|
|
19807
|
-
const parsed =
|
|
20411
|
+
const parsed = matter16(raw);
|
|
19808
20412
|
const data = parsed.data;
|
|
19809
20413
|
const affectedModules = Array.isArray(data.affected_modules) ? data.affected_modules.map(String) : [];
|
|
19810
20414
|
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) : [] })) : [];
|
|
@@ -19857,7 +20461,7 @@ function getWorkItemContextTool(root, args) {
|
|
|
19857
20461
|
if (!wi) return fail2(`Work Item "${args.workItemId}" not found.`);
|
|
19858
20462
|
const raw = readText(root, wi.path);
|
|
19859
20463
|
if (!raw) return fail2(`Could not read Work Item file.`);
|
|
19860
|
-
const parsed =
|
|
20464
|
+
const parsed = matter16(raw);
|
|
19861
20465
|
const data = parsed.data;
|
|
19862
20466
|
const affectedModules = Array.isArray(data.affected_modules) ? data.affected_modules.map(String) : [];
|
|
19863
20467
|
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) : [] })) : [];
|
|
@@ -19911,15 +20515,15 @@ function extractSection(content, heading) {
|
|
|
19911
20515
|
return out;
|
|
19912
20516
|
}
|
|
19913
20517
|
function buildBranchStrategy(wi, affectedModules, codeEntries, descriptor) {
|
|
19914
|
-
const
|
|
20518
|
+
const slug6 = slugify4(wi.title);
|
|
19915
20519
|
const wiLower = wi.id.toLowerCase();
|
|
19916
|
-
const coreBranch = `feature/${wiLower}-${
|
|
20520
|
+
const coreBranch = `feature/${wiLower}-${slug6}`;
|
|
19917
20521
|
const moduleBranches = {};
|
|
19918
20522
|
const commitMessages = {};
|
|
19919
20523
|
for (const modId of affectedModules) {
|
|
19920
20524
|
const mod = (descriptor.modules ?? []).find((m2) => m2.id === modId);
|
|
19921
20525
|
const repo = mod ? moduleRepoPath(mod) || modId : modId;
|
|
19922
|
-
moduleBranches[repo] = `feature/${wiLower}-${modId}-${
|
|
20526
|
+
moduleBranches[repo] = `feature/${wiLower}-${modId}-${slug6.slice(0, 20)}`;
|
|
19923
20527
|
commitMessages[repo] = `fix(${wiLower}): update ${modId}`;
|
|
19924
20528
|
}
|
|
19925
20529
|
commitMessages["core"] = `docs(${wiLower}): capture cross-repo implementation summary`;
|
|
@@ -19943,7 +20547,7 @@ function suggestBranchStrategyTool(root, args) {
|
|
|
19943
20547
|
if (!wi) return fail2(`Work Item "${args.workItemId}" not found.`);
|
|
19944
20548
|
const raw = readText(root, wi.path);
|
|
19945
20549
|
if (!raw) return fail2(`Could not read Work Item file.`);
|
|
19946
|
-
const parsed =
|
|
20550
|
+
const parsed = matter16(raw);
|
|
19947
20551
|
const data = parsed.data;
|
|
19948
20552
|
const affectedModules = Array.isArray(data.affected_modules) ? data.affected_modules.map(String) : [];
|
|
19949
20553
|
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) : [] })) : [];
|
|
@@ -20092,31 +20696,31 @@ function modulesDiscoverTool(root, args) {
|
|
|
20092
20696
|
const sysName = config.system?.name ?? config.project?.name ?? "";
|
|
20093
20697
|
const workspaceRoots2 = config.multirepo?.workspace_roots ?? [".."];
|
|
20094
20698
|
const IGNORED = /* @__PURE__ */ new Set([".git", "node_modules", "dist", "build", "coverage", "vendor", ".next", "out", "tmp", "temp", ".kaddo"]);
|
|
20095
|
-
const coreAbs =
|
|
20699
|
+
const coreAbs = path6.resolve(root);
|
|
20096
20700
|
const discovered = [];
|
|
20097
20701
|
for (const wr of workspaceRoots2) {
|
|
20098
|
-
const rootAbs =
|
|
20099
|
-
if (!
|
|
20702
|
+
const rootAbs = path6.resolve(root, wr);
|
|
20703
|
+
if (!fs7.existsSync(rootAbs) || !fs7.statSync(rootAbs).isDirectory()) continue;
|
|
20100
20704
|
let entries;
|
|
20101
20705
|
try {
|
|
20102
|
-
entries =
|
|
20706
|
+
entries = fs7.readdirSync(rootAbs);
|
|
20103
20707
|
} catch {
|
|
20104
20708
|
continue;
|
|
20105
20709
|
}
|
|
20106
20710
|
for (const entry of entries) {
|
|
20107
20711
|
if (IGNORED.has(entry)) continue;
|
|
20108
|
-
const candidateAbs =
|
|
20109
|
-
if (!
|
|
20712
|
+
const candidateAbs = path6.resolve(rootAbs, entry);
|
|
20713
|
+
if (!fs7.statSync(candidateAbs).isDirectory()) continue;
|
|
20110
20714
|
if (candidateAbs === coreAbs) continue;
|
|
20111
|
-
const relPath =
|
|
20112
|
-
const cfgPath =
|
|
20113
|
-
if (!
|
|
20715
|
+
const relPath = path6.relative(root, candidateAbs).replace(/\\/g, "/");
|
|
20716
|
+
const cfgPath = path6.join(candidateAbs, ".kaddo", "config.yml");
|
|
20717
|
+
if (!fs7.existsSync(cfgPath)) {
|
|
20114
20718
|
discovered.push({ id: entry, path: relPath, status: "not_configured", eligibleForMapping: false });
|
|
20115
20719
|
continue;
|
|
20116
20720
|
}
|
|
20117
20721
|
let parsed = null;
|
|
20118
20722
|
try {
|
|
20119
|
-
parsed = parseYaml11(
|
|
20723
|
+
parsed = parseYaml11(fs7.readFileSync(cfgPath, "utf-8"));
|
|
20120
20724
|
} catch {
|
|
20121
20725
|
}
|
|
20122
20726
|
const role = parsed?.project?.role ?? parsed?.multirepo?.role;
|
|
@@ -20190,8 +20794,8 @@ ${list3(data.owners)}
|
|
|
20190
20794
|
|
|
20191
20795
|
// ../cli/src/core/system-topology.ts
|
|
20192
20796
|
import { parse as parseYaml12, stringify as stringifyYaml8 } from "yaml";
|
|
20193
|
-
import
|
|
20194
|
-
import
|
|
20797
|
+
import fs8 from "fs";
|
|
20798
|
+
import path7 from "path";
|
|
20195
20799
|
import crypto from "crypto";
|
|
20196
20800
|
var TOPOLOGY_FILE2 = "knowledge/tech/system-topology.yml";
|
|
20197
20801
|
var SYSTEM_ENTITY_KINDS2 = /* @__PURE__ */ new Set([
|
|
@@ -20563,16 +21167,16 @@ function findSystemPaths(dir, source, target, opts = {}) {
|
|
|
20563
21167
|
out.get(r2.source).push(r2.target);
|
|
20564
21168
|
}
|
|
20565
21169
|
const paths = [];
|
|
20566
|
-
const walk = (node,
|
|
21170
|
+
const walk = (node, path9) => {
|
|
20567
21171
|
if (paths.length >= 20) return;
|
|
20568
|
-
if (node === target &&
|
|
20569
|
-
paths.push([...
|
|
21172
|
+
if (node === target && path9.length > 1) {
|
|
21173
|
+
paths.push([...path9]);
|
|
20570
21174
|
return;
|
|
20571
21175
|
}
|
|
20572
|
-
if (
|
|
21176
|
+
if (path9.length > maxDepth) return;
|
|
20573
21177
|
for (const nxt of out.get(node) ?? []) {
|
|
20574
|
-
if (
|
|
20575
|
-
walk(nxt, [...
|
|
21178
|
+
if (path9.includes(nxt)) continue;
|
|
21179
|
+
walk(nxt, [...path9, nxt]);
|
|
20576
21180
|
}
|
|
20577
21181
|
};
|
|
20578
21182
|
walk(source, [source]);
|
|
@@ -20597,14 +21201,14 @@ function getImpactCandidates(dir, seedIds, opts = {}) {
|
|
|
20597
21201
|
existing.reason.push(reason);
|
|
20598
21202
|
continue;
|
|
20599
21203
|
}
|
|
20600
|
-
const
|
|
21204
|
+
const path9 = findSystemPaths(dir, seed, n.id, { maxDepth: opts.maxDepth ?? DEFAULT_MAX_DEPTH })[0];
|
|
20601
21205
|
candidates.set(n.id, {
|
|
20602
21206
|
nodeId: n.id,
|
|
20603
21207
|
label: n.label,
|
|
20604
21208
|
kind: n.type,
|
|
20605
21209
|
...n.moduleId ? { moduleId: n.moduleId } : {},
|
|
20606
21210
|
reason: [reason],
|
|
20607
|
-
...
|
|
21211
|
+
...path9 ? { graphPath: path9.map((id) => byId.get(id)?.label ?? id) } : {},
|
|
20608
21212
|
...n.implementationRefs?.length ? { implementationRefs: n.implementationRefs } : {},
|
|
20609
21213
|
...n.knowledgeRefs?.length ? { knowledgeRefs: n.knowledgeRefs } : {},
|
|
20610
21214
|
status: "candidate",
|
|
@@ -20703,14 +21307,14 @@ function systemImpactCandidatesTool(root, args) {
|
|
|
20703
21307
|
}
|
|
20704
21308
|
|
|
20705
21309
|
// ../cli/src/services/integrations.ts
|
|
20706
|
-
import
|
|
21310
|
+
import matter18 from "gray-matter";
|
|
20707
21311
|
import { parse as parseYaml13, stringify as stringifyYaml9 } from "yaml";
|
|
20708
21312
|
|
|
20709
21313
|
// ../cli/src/core/work-item-write.ts
|
|
20710
|
-
import
|
|
20711
|
-
import
|
|
21314
|
+
import fs9 from "fs";
|
|
21315
|
+
import path8 from "path";
|
|
20712
21316
|
import crypto4 from "crypto";
|
|
20713
|
-
import
|
|
21317
|
+
import matter17 from "gray-matter";
|
|
20714
21318
|
var SECTION_ORDER2 = [
|
|
20715
21319
|
"Actor",
|
|
20716
21320
|
"Outcome",
|
|
@@ -21378,8 +21982,8 @@ async function handleJiraResponse2(res) {
|
|
|
21378
21982
|
}
|
|
21379
21983
|
return await res.json();
|
|
21380
21984
|
}
|
|
21381
|
-
async function jiraFetch2(baseUrl,
|
|
21382
|
-
const url = new URL(
|
|
21985
|
+
async function jiraFetch2(baseUrl, path9, auth, timeoutMs, params) {
|
|
21986
|
+
const url = new URL(path9, baseUrl + "/");
|
|
21383
21987
|
if (params) for (const [k2, v] of Object.entries(params)) url.searchParams.set(k2, v);
|
|
21384
21988
|
const controller = new AbortController();
|
|
21385
21989
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
@@ -21397,8 +22001,8 @@ async function jiraFetch2(baseUrl, path7, auth, timeoutMs, params) {
|
|
|
21397
22001
|
clearTimeout(timer);
|
|
21398
22002
|
}
|
|
21399
22003
|
}
|
|
21400
|
-
async function jiraPost2(baseUrl,
|
|
21401
|
-
const url = new URL(
|
|
22004
|
+
async function jiraPost2(baseUrl, path9, auth, timeoutMs, body) {
|
|
22005
|
+
const url = new URL(path9, baseUrl + "/");
|
|
21402
22006
|
const controller = new AbortController();
|
|
21403
22007
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
21404
22008
|
try {
|
|
@@ -21758,7 +22362,7 @@ async function integrationsWorkItemTool(root, args) {
|
|
|
21758
22362
|
}
|
|
21759
22363
|
|
|
21760
22364
|
// ../cli/src/core/context-pack.ts
|
|
21761
|
-
import
|
|
22365
|
+
import matter19 from "gray-matter";
|
|
21762
22366
|
var CONTEXT_PACK_VERSION = "1";
|
|
21763
22367
|
var ARCH_DIR4 = "knowledge";
|
|
21764
22368
|
function readScanJson(dir) {
|
|
@@ -21771,7 +22375,7 @@ function readScanJson(dir) {
|
|
|
21771
22375
|
}
|
|
21772
22376
|
}
|
|
21773
22377
|
function firstParagraph4(markdown) {
|
|
21774
|
-
const body =
|
|
22378
|
+
const body = matter19(markdown).content.trim();
|
|
21775
22379
|
const para = body.split("\n\n").map((p) => p.trim()).find((p) => p && !p.startsWith("#"));
|
|
21776
22380
|
return para ?? "";
|
|
21777
22381
|
}
|
|
@@ -21825,6 +22429,8 @@ function toContextWorkItem(a2) {
|
|
|
21825
22429
|
wi.validationStatus = a2.validationStatus || (isHistorical ? "not-assessed" : void 0);
|
|
21826
22430
|
wi.releaseStatus = a2.releaseStatus || (isHistorical ? "not-assessed" : void 0);
|
|
21827
22431
|
if (a2.affectedModules.length > 0) wi.affectedModules = a2.affectedModules;
|
|
22432
|
+
const wiResources = parseWorkItemResources(a2.rawFrontmatter);
|
|
22433
|
+
if (wiResources.length > 0) wi.resources = wiResources;
|
|
21828
22434
|
if (a2.scopeConfidence) wi.scopeConfidence = a2.scopeConfidence;
|
|
21829
22435
|
if (a2.moduleCoverage) wi.moduleCoverage = a2.moduleCoverage;
|
|
21830
22436
|
if (a2.impactAnalysis) wi.impactAnalysis = a2.impactAnalysis;
|
|
@@ -22736,7 +23342,7 @@ function requireKnowledge(root) {
|
|
|
22736
23342
|
throw new KaddoMcpError("Knowledge repository not found. Run `kaddo bootstrap` first.");
|
|
22737
23343
|
}
|
|
22738
23344
|
}
|
|
22739
|
-
function
|
|
23345
|
+
function slug5(s) {
|
|
22740
23346
|
return s.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "project";
|
|
22741
23347
|
}
|
|
22742
23348
|
function generateContext(root) {
|
|
@@ -22869,7 +23475,7 @@ function generateCapsuleDraft(root) {
|
|
|
22869
23475
|
const config = requireConfig(root);
|
|
22870
23476
|
requireKnowledge(root);
|
|
22871
23477
|
const capsule = buildCapsule(root, config);
|
|
22872
|
-
const name =
|
|
23478
|
+
const name = slug5(config.project.name);
|
|
22873
23479
|
const md = `.kaddo/exports/${name}.capsule.md`;
|
|
22874
23480
|
const json = `.kaddo/exports/${name}.capsule.json`;
|
|
22875
23481
|
writeDerived(root, md, renderCapsuleMarkdown(capsule));
|
|
@@ -23157,6 +23763,63 @@ function createServer(root) {
|
|
|
23157
23763
|
},
|
|
23158
23764
|
async (args) => toolText(guarded(root, () => markWorkItemReady(root, args.id, args.confirm)))
|
|
23159
23765
|
);
|
|
23766
|
+
server.registerTool(
|
|
23767
|
+
"kaddo_list_resources",
|
|
23768
|
+
{ title: "List Project Resources", description: "List external systems the project uses (databases, cloud, APIs\u2026), read-only, no secrets.", inputSchema: {} },
|
|
23769
|
+
async () => toolText(guarded(root, () => listResourcesTool(root)))
|
|
23770
|
+
);
|
|
23771
|
+
server.registerTool(
|
|
23772
|
+
"kaddo_get_resource",
|
|
23773
|
+
{ title: "Get Project Resource", description: "Get one Project Resource by ID: purpose, environments, access interfaces, boundaries and auth references (never values).", inputSchema: { id: z4.string() } },
|
|
23774
|
+
async (args) => toolText(guarded(root, () => getResourceTool(root, args.id)))
|
|
23775
|
+
);
|
|
23776
|
+
const resourceFields = {
|
|
23777
|
+
title: z4.string().optional(),
|
|
23778
|
+
resource_type: z4.string().optional(),
|
|
23779
|
+
provider: z4.string().optional(),
|
|
23780
|
+
environments: z4.array(z4.string()).optional(),
|
|
23781
|
+
scope: z4.string().optional(),
|
|
23782
|
+
module: z4.string().optional(),
|
|
23783
|
+
modules: z4.array(z4.string()).optional(),
|
|
23784
|
+
purpose: z4.string().optional(),
|
|
23785
|
+
access_interfaces: z4.array(z4.object({
|
|
23786
|
+
type: z4.string(),
|
|
23787
|
+
tool: z4.string().optional(),
|
|
23788
|
+
provider: z4.string().optional(),
|
|
23789
|
+
purpose: z4.string().optional(),
|
|
23790
|
+
operations: z4.array(z4.string()).optional(),
|
|
23791
|
+
environments: z4.array(z4.string()).optional(),
|
|
23792
|
+
constraints: z4.string().optional()
|
|
23793
|
+
})).optional(),
|
|
23794
|
+
authentication: z4.object({ mode: z4.string().optional(), refs: z4.array(z4.string()).optional() }).optional()
|
|
23795
|
+
};
|
|
23796
|
+
server.registerTool(
|
|
23797
|
+
"kaddo_create_resource",
|
|
23798
|
+
{
|
|
23799
|
+
title: "Create Project Resource",
|
|
23800
|
+
description: "Propose creating a Project Resource. Without confirm, returns a preview + validation findings (writes nothing). With confirm=true, Core persists it as Markdown. Never include credential values \u2014 only reference names.",
|
|
23801
|
+
inputSchema: { ...resourceFields, confirm: z4.boolean().optional() }
|
|
23802
|
+
},
|
|
23803
|
+
async (args) => toolText(guarded(root, () => createResourceTool(root, args, args.confirm)))
|
|
23804
|
+
);
|
|
23805
|
+
server.registerTool(
|
|
23806
|
+
"kaddo_update_resource",
|
|
23807
|
+
{
|
|
23808
|
+
title: "Update Project Resource",
|
|
23809
|
+
description: "Propose updating a Project Resource (no-lossy). Without confirm, returns a preview; with confirm=true, Core applies it. Never include credential values.",
|
|
23810
|
+
inputSchema: { id: z4.string(), ...resourceFields, confirm: z4.boolean().optional() }
|
|
23811
|
+
},
|
|
23812
|
+
async (args) => toolText(guarded(root, () => updateResourceTool(root, args.id, args, args.confirm)))
|
|
23813
|
+
);
|
|
23814
|
+
server.registerTool(
|
|
23815
|
+
"kaddo_delete_resource",
|
|
23816
|
+
{
|
|
23817
|
+
title: "Delete Project Resource",
|
|
23818
|
+
description: "Delete a Project Resource. Without confirm, returns what references it (writes nothing); with confirm=true, Core removes only the resource \u2014 references in other artifacts are left intact.",
|
|
23819
|
+
inputSchema: { id: z4.string(), confirm: z4.boolean().optional() }
|
|
23820
|
+
},
|
|
23821
|
+
async (args) => toolText(guarded(root, () => deleteResourceTool(root, args.id, args.confirm)))
|
|
23822
|
+
);
|
|
23160
23823
|
server.registerTool(
|
|
23161
23824
|
"kaddo_list_capsules",
|
|
23162
23825
|
{ title: "List Knowledge Capsules", description: "List registered external Knowledge Capsules.", inputSchema: {} },
|