@kaddo/mcp 3.113.0 → 3.115.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 +403 -64
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -386,7 +386,7 @@ function join(...parts) {
|
|
|
386
386
|
}
|
|
387
387
|
|
|
388
388
|
// ../cli/src/core/impact-report.ts
|
|
389
|
-
import
|
|
389
|
+
import matter10 from "gray-matter";
|
|
390
390
|
|
|
391
391
|
// ../cli/src/core/config.ts
|
|
392
392
|
import { z } from "zod";
|
|
@@ -2590,8 +2590,117 @@ function discoverInitiatives(dir) {
|
|
|
2590
2590
|
return out.sort((a2, b2) => a2.id.localeCompare(b2.id));
|
|
2591
2591
|
}
|
|
2592
2592
|
|
|
2593
|
+
// ../cli/src/core/resources.ts
|
|
2594
|
+
import matter6 from "gray-matter";
|
|
2595
|
+
var RESOURCE_ARTIFACT_TYPE = "project-resource";
|
|
2596
|
+
function str(v) {
|
|
2597
|
+
return typeof v === "string" && v.trim() ? v.trim() : null;
|
|
2598
|
+
}
|
|
2599
|
+
function strArray(v) {
|
|
2600
|
+
if (Array.isArray(v)) return v.map((x2) => String(x2).trim()).filter(Boolean);
|
|
2601
|
+
if (typeof v === "string" && v.trim()) return [v.trim()];
|
|
2602
|
+
return [];
|
|
2603
|
+
}
|
|
2604
|
+
function parseInterfaces(v) {
|
|
2605
|
+
if (!Array.isArray(v)) return [];
|
|
2606
|
+
const out = [];
|
|
2607
|
+
for (const raw of v) {
|
|
2608
|
+
if (!raw || typeof raw !== "object") continue;
|
|
2609
|
+
const o2 = raw;
|
|
2610
|
+
const type = str(o2.type);
|
|
2611
|
+
if (!type) continue;
|
|
2612
|
+
out.push({
|
|
2613
|
+
type,
|
|
2614
|
+
tool: str(o2.tool),
|
|
2615
|
+
provider: str(o2.provider),
|
|
2616
|
+
purpose: str(o2.purpose),
|
|
2617
|
+
operations: strArray(o2.operations),
|
|
2618
|
+
environments: strArray(o2.environments),
|
|
2619
|
+
constraints: str(o2.constraints)
|
|
2620
|
+
});
|
|
2621
|
+
}
|
|
2622
|
+
return out;
|
|
2623
|
+
}
|
|
2624
|
+
function parseBoundaries(v) {
|
|
2625
|
+
if (!v || typeof v !== "object" || Array.isArray(v)) return {};
|
|
2626
|
+
const out = {};
|
|
2627
|
+
for (const [env, val] of Object.entries(v)) {
|
|
2628
|
+
const list3 = strArray(val);
|
|
2629
|
+
if (list3.length > 0) out[env] = list3;
|
|
2630
|
+
}
|
|
2631
|
+
return out;
|
|
2632
|
+
}
|
|
2633
|
+
function parseAuth(v) {
|
|
2634
|
+
if (!v || typeof v !== "object" || Array.isArray(v)) return { mode: null, refs: [] };
|
|
2635
|
+
const o2 = v;
|
|
2636
|
+
const refs = strArray(o2.refs).filter((r2) => !r2.includes("=") && /^[A-Za-z0-9_.-]+$/.test(r2));
|
|
2637
|
+
return { mode: str(o2.mode), refs };
|
|
2638
|
+
}
|
|
2639
|
+
function toDetail(filePath, relPath, raw) {
|
|
2640
|
+
const parsed = matter6(raw);
|
|
2641
|
+
const fm = parsed.data ?? {};
|
|
2642
|
+
const body = parsed.content.trim();
|
|
2643
|
+
const id = str(fm.id) ?? "";
|
|
2644
|
+
return {
|
|
2645
|
+
id,
|
|
2646
|
+
title: str(fm.title) ?? id,
|
|
2647
|
+
resourceType: str(fm.resource_type),
|
|
2648
|
+
provider: str(fm.provider),
|
|
2649
|
+
environments: strArray(fm.environments),
|
|
2650
|
+
path: relPath,
|
|
2651
|
+
purpose: str(fm.purpose),
|
|
2652
|
+
interfaces: parseInterfaces(fm.access_interfaces),
|
|
2653
|
+
boundaries: parseBoundaries(fm.access_boundaries),
|
|
2654
|
+
...(() => {
|
|
2655
|
+
const a2 = parseAuth(fm.authentication);
|
|
2656
|
+
return { authRefs: a2.refs, authMode: a2.mode };
|
|
2657
|
+
})(),
|
|
2658
|
+
markdownBody: body
|
|
2659
|
+
};
|
|
2660
|
+
}
|
|
2661
|
+
function getResources(dir) {
|
|
2662
|
+
return discoverKnowledge(dir).filter((a2) => a2.type === RESOURCE_ARTIFACT_TYPE).map((a2) => {
|
|
2663
|
+
const fm = a2.rawFrontmatter;
|
|
2664
|
+
return {
|
|
2665
|
+
id: a2.id || str(fm.id) || a2.title,
|
|
2666
|
+
title: a2.title || str(fm.title) || a2.id,
|
|
2667
|
+
resourceType: str(fm.resource_type),
|
|
2668
|
+
provider: str(fm.provider),
|
|
2669
|
+
environments: strArray(fm.environments),
|
|
2670
|
+
path: a2.relPath
|
|
2671
|
+
};
|
|
2672
|
+
}).sort((x2, y) => x2.id.localeCompare(y.id));
|
|
2673
|
+
}
|
|
2674
|
+
function getResource(dir, id) {
|
|
2675
|
+
const match = discoverKnowledge(dir).find(
|
|
2676
|
+
(a2) => a2.type === RESOURCE_ARTIFACT_TYPE && (a2.id === id || a2.title === id)
|
|
2677
|
+
);
|
|
2678
|
+
if (!match) return null;
|
|
2679
|
+
const raw = readFile(match.filePath);
|
|
2680
|
+
return toDetail(match.filePath, match.relPath, raw);
|
|
2681
|
+
}
|
|
2682
|
+
function parseWorkItemResources(fm) {
|
|
2683
|
+
const v = fm.resources;
|
|
2684
|
+
if (!Array.isArray(v)) return [];
|
|
2685
|
+
const out = [];
|
|
2686
|
+
for (const raw of v) {
|
|
2687
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) continue;
|
|
2688
|
+
const o2 = raw;
|
|
2689
|
+
const id = str(o2.id);
|
|
2690
|
+
if (!id) continue;
|
|
2691
|
+
out.push({ id, role: str(o2.role) ?? "affected" });
|
|
2692
|
+
}
|
|
2693
|
+
return out;
|
|
2694
|
+
}
|
|
2695
|
+
|
|
2593
2696
|
// ../cli/src/core/graph.ts
|
|
2594
2697
|
var KNOWLEDGE3 = "knowledge";
|
|
2698
|
+
var RESOURCE_ROLE_EDGE = {
|
|
2699
|
+
affected: "affects",
|
|
2700
|
+
implementation: "uses",
|
|
2701
|
+
validation: "validates_with",
|
|
2702
|
+
delivery: "delivers_through"
|
|
2703
|
+
};
|
|
2595
2704
|
var ACTIVE_STATUSES = ["draft", "ready", "in-progress", "blocked"];
|
|
2596
2705
|
var ALL_STATUSES = ["draft", "ready", "in-progress", "blocked", "completed"];
|
|
2597
2706
|
function scopeStatuses(scope) {
|
|
@@ -2670,6 +2779,16 @@ function buildGraph(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
|
|
|
2670
2779
|
if (cand.materializedAs) addEdge(candId, `wi:${cand.materializedAs}`, "materialized_as");
|
|
2671
2780
|
}
|
|
2672
2781
|
}
|
|
2782
|
+
const resourceTitleById = /* @__PURE__ */ new Map();
|
|
2783
|
+
for (const r2 of getResources(dir)) {
|
|
2784
|
+
resourceTitleById.set(r2.id, r2.title);
|
|
2785
|
+
addNode({
|
|
2786
|
+
id: `resource:${r2.id}`,
|
|
2787
|
+
type: "project-resource",
|
|
2788
|
+
label: r2.title ? `${r2.id} ${r2.title}`.trim() : r2.id,
|
|
2789
|
+
path: r2.path
|
|
2790
|
+
});
|
|
2791
|
+
}
|
|
2673
2792
|
for (const wi of selectedWIs) {
|
|
2674
2793
|
const id = wi.id || wi.title;
|
|
2675
2794
|
if (!id || !id.trim()) continue;
|
|
@@ -2694,6 +2813,12 @@ function buildGraph(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
|
|
|
2694
2813
|
addNode({ id: capId, type: "capability", label: cap });
|
|
2695
2814
|
addEdge(wiNodeId, capId, "implements");
|
|
2696
2815
|
}
|
|
2816
|
+
for (const rel of parseWorkItemResources(wi.rawFrontmatter)) {
|
|
2817
|
+
const resId = `resource:${rel.id}`;
|
|
2818
|
+
const title = resourceTitleById.get(rel.id);
|
|
2819
|
+
addNode({ id: resId, type: "project-resource", label: title ? `${rel.id} ${title}`.trim() : rel.id });
|
|
2820
|
+
addEdge(wiNodeId, resId, RESOURCE_ROLE_EDGE[rel.role] ?? "uses");
|
|
2821
|
+
}
|
|
2697
2822
|
for (const dec of wi.decisions) {
|
|
2698
2823
|
if (!dec || !dec.trim()) continue;
|
|
2699
2824
|
const adrId = `adr:${dec}`;
|
|
@@ -3072,7 +3197,7 @@ function loadGraphHints(dir) {
|
|
|
3072
3197
|
}
|
|
3073
3198
|
|
|
3074
3199
|
// ../cli/src/services/installed-skills.ts
|
|
3075
|
-
import
|
|
3200
|
+
import matter7 from "gray-matter";
|
|
3076
3201
|
var SKILLS_DIR = join("knowledge", "skills");
|
|
3077
3202
|
function discoverInstalledSkills(dir) {
|
|
3078
3203
|
const base = join(dir, SKILLS_DIR);
|
|
@@ -3082,7 +3207,7 @@ function discoverInstalledSkills(dir) {
|
|
|
3082
3207
|
const skillFile = join(base, entry, "skill.md");
|
|
3083
3208
|
if (!isFile(skillFile)) continue;
|
|
3084
3209
|
try {
|
|
3085
|
-
const { data } =
|
|
3210
|
+
const { data } = matter7(readFile(skillFile));
|
|
3086
3211
|
if (data.type && String(data.type) !== "skill") continue;
|
|
3087
3212
|
const id = String(data.id ?? entry);
|
|
3088
3213
|
out.push({
|
|
@@ -4671,7 +4796,7 @@ function deriveDeliveryCompletedStatus(dir) {
|
|
|
4671
4796
|
}
|
|
4672
4797
|
|
|
4673
4798
|
// ../cli/src/core/assets.ts
|
|
4674
|
-
import
|
|
4799
|
+
import matter8 from "gray-matter";
|
|
4675
4800
|
|
|
4676
4801
|
// ../cli/src/core/version.ts
|
|
4677
4802
|
import { fileURLToPath } from "url";
|
|
@@ -4826,8 +4951,13 @@ dependencies.
|
|
|
4826
4951
|
flags, content/copy, authentication/authorization, notifications, analytics, documentation,
|
|
4827
4952
|
operations/release \u2014 as affected, reviewed-not-affected, unknown, or not-applicable.
|
|
4828
4953
|
4. **Module review** \u2014 for multirepo, evaluate each mapped module with the same statuses.
|
|
4829
|
-
5. **
|
|
4830
|
-
|
|
4954
|
+
5. **Resource review** \u2014 ask: does this change need to observe, modify, validate or deliver against a
|
|
4955
|
+
known external resource of the project (a Project Resource under \`knowledge/tech/resources/\` \u2014
|
|
4956
|
+
database, cloud, API, queue, storage\u2026)? If so, declare it in the WI front matter \`resources:\` with
|
|
4957
|
+
a role (\`affected\`, \`implementation\`, \`validation\`, \`delivery\`). Resources are external systems,
|
|
4958
|
+
distinct from \`affected_modules\` (code). Not every WI needs resources (e.g. a CSS change does not).
|
|
4959
|
+
6. **Completeness review** \u2014 confirm: outcome covered, journey covered, modules assessed, resources
|
|
4960
|
+
assessed, unknowns visible, acceptance criteria end-to-end, scope and out-of-scope coherent.
|
|
4831
4961
|
|
|
4832
4962
|
## Rules
|
|
4833
4963
|
|
|
@@ -4838,6 +4968,8 @@ dependencies.
|
|
|
4838
4968
|
- Include at least one end-to-end acceptance criterion for user-facing changes.
|
|
4839
4969
|
- Do not reduce a product intent to the first technical implementation found.
|
|
4840
4970
|
- Evaluate surfaces and modules before proposing files.
|
|
4971
|
+
- Consider Project Resources when the scope justifies it; never invent resources not backed by
|
|
4972
|
+
project knowledge, and keep \`resources\` (external systems) separate from \`affected_modules\` (code).
|
|
4841
4973
|
- This skill refines scope and acceptance criteria but does not approve implementation readiness.
|
|
4842
4974
|
Readiness requires human confirmation through \`kaddo ready\` or the MCP \`mark_work_item_ready\` action.
|
|
4843
4975
|
|
|
@@ -7877,7 +8009,7 @@ function canonicalSkills() {
|
|
|
7877
8009
|
}
|
|
7878
8010
|
function versionOf(content) {
|
|
7879
8011
|
try {
|
|
7880
|
-
const v =
|
|
8012
|
+
const v = matter8(content).data.version;
|
|
7881
8013
|
return v === void 0 || v === null ? null : String(v);
|
|
7882
8014
|
} catch {
|
|
7883
8015
|
return null;
|
|
@@ -8649,7 +8781,7 @@ function renderRouteCompact(route) {
|
|
|
8649
8781
|
}
|
|
8650
8782
|
|
|
8651
8783
|
// ../cli/src/core/metadata-health.ts
|
|
8652
|
-
import
|
|
8784
|
+
import matter9 from "gray-matter";
|
|
8653
8785
|
var KNOWLEDGE_FILES2 = [
|
|
8654
8786
|
"knowledge/business/business.md",
|
|
8655
8787
|
"knowledge/product/product.md",
|
|
@@ -8669,7 +8801,7 @@ function analyzeMetadataHealth(dir) {
|
|
|
8669
8801
|
let data;
|
|
8670
8802
|
try {
|
|
8671
8803
|
const raw = readFile(p);
|
|
8672
|
-
data =
|
|
8804
|
+
data = matter9(raw).data;
|
|
8673
8805
|
} catch {
|
|
8674
8806
|
continue;
|
|
8675
8807
|
}
|
|
@@ -8701,7 +8833,7 @@ function analyzeMetadataHealth(dir) {
|
|
|
8701
8833
|
if (isFile(p) && entry.endsWith(".md")) {
|
|
8702
8834
|
try {
|
|
8703
8835
|
const raw = readFile(p);
|
|
8704
|
-
const fm =
|
|
8836
|
+
const fm = matter9(raw).data;
|
|
8705
8837
|
if (fm.status === "done") {
|
|
8706
8838
|
const rel = p.replace(dir + "/", "").replace(dir + "\\", "").replace(/\\/g, "/");
|
|
8707
8839
|
findings.push({
|
|
@@ -9656,7 +9788,7 @@ function buildImpactReport(dir, opts = {}, now = /* @__PURE__ */ new Date()) {
|
|
|
9656
9788
|
}
|
|
9657
9789
|
}
|
|
9658
9790
|
try {
|
|
9659
|
-
const body =
|
|
9791
|
+
const body = matter10(readFile(wi.filePath)).content;
|
|
9660
9792
|
if (hasSection(body, /acceptance|criterios de aceptaci/i)) withAcceptance++;
|
|
9661
9793
|
else gaps.missing_acceptance_criteria.push(gapItem(wi, "Add an `## Acceptance Criteria` section."));
|
|
9662
9794
|
if (hasSection(body, /definition of done|^#{1,6}\s*dod\b|definici[oó]n de (terminado|hecho)/i)) withDoD++;
|
|
@@ -10369,7 +10501,7 @@ function serializeDriftJson(r2) {
|
|
|
10369
10501
|
}
|
|
10370
10502
|
|
|
10371
10503
|
// src/workitems.ts
|
|
10372
|
-
import
|
|
10504
|
+
import matter11 from "gray-matter";
|
|
10373
10505
|
var VALID_SOURCES2 = /* @__PURE__ */ new Set(["manual", "roadmap", "jira", "github", "notion", "xlsx", "csv", "api", "external", "unknown"]);
|
|
10374
10506
|
function optStr3(v) {
|
|
10375
10507
|
return typeof v === "string" && v.trim() ? v.trim() : void 0;
|
|
@@ -10392,7 +10524,7 @@ function parseSource(data) {
|
|
|
10392
10524
|
return { type: "unknown", inferred: true };
|
|
10393
10525
|
}
|
|
10394
10526
|
var ACTIVE_STATES2 = /* @__PURE__ */ new Set(["draft", "ready", "in-progress", "blocked"]);
|
|
10395
|
-
function
|
|
10527
|
+
function strArray2(v) {
|
|
10396
10528
|
return Array.isArray(v) ? v.map((x2) => String(x2)).filter(Boolean) : [];
|
|
10397
10529
|
}
|
|
10398
10530
|
function lifecycleOf(data, relPath) {
|
|
@@ -10460,15 +10592,15 @@ function listWorkItems(root) {
|
|
|
10460
10592
|
if (raw === null) continue;
|
|
10461
10593
|
let parsed;
|
|
10462
10594
|
try {
|
|
10463
|
-
parsed =
|
|
10595
|
+
parsed = matter11(raw);
|
|
10464
10596
|
} catch {
|
|
10465
10597
|
continue;
|
|
10466
10598
|
}
|
|
10467
10599
|
const data = parsed.data;
|
|
10468
10600
|
if (!data.type) continue;
|
|
10469
10601
|
const status = lifecycleOf(data, rel);
|
|
10470
|
-
const code =
|
|
10471
|
-
const domains =
|
|
10602
|
+
const code = strArray2(data.code);
|
|
10603
|
+
const domains = strArray2(data.domains);
|
|
10472
10604
|
const source = parseSource(data);
|
|
10473
10605
|
const readiness = status === "draft" ? assessReadiness(data, parsed.content, code, domains, source) : void 0;
|
|
10474
10606
|
items.push({
|
|
@@ -10480,9 +10612,9 @@ function listWorkItems(root) {
|
|
|
10480
10612
|
path: rel,
|
|
10481
10613
|
summary: String(data.summary ?? "") || firstParagraph2(parsed.content),
|
|
10482
10614
|
code,
|
|
10483
|
-
capabilities:
|
|
10484
|
-
decisions:
|
|
10485
|
-
capsules:
|
|
10615
|
+
capabilities: strArray2(data.capabilities),
|
|
10616
|
+
decisions: strArray2(data.decisions),
|
|
10617
|
+
capsules: strArray2(data.capsules),
|
|
10486
10618
|
source,
|
|
10487
10619
|
domains,
|
|
10488
10620
|
readiness,
|
|
@@ -10928,14 +11060,14 @@ function getPrompt(root, name) {
|
|
|
10928
11060
|
}
|
|
10929
11061
|
|
|
10930
11062
|
// src/tools.ts
|
|
10931
|
-
import
|
|
11063
|
+
import matter13 from "gray-matter";
|
|
10932
11064
|
|
|
10933
11065
|
// ../cli/dist/core.js
|
|
10934
11066
|
import fs3 from "fs";
|
|
10935
11067
|
import path3 from "path";
|
|
10936
11068
|
import { z as z3 } from "zod";
|
|
10937
11069
|
import { parse as parseYaml10 } from "yaml";
|
|
10938
|
-
import
|
|
11070
|
+
import matter12 from "gray-matter";
|
|
10939
11071
|
import matter22 from "gray-matter";
|
|
10940
11072
|
import { parse as parseYaml62, stringify as stringifyYaml32 } from "yaml";
|
|
10941
11073
|
import { parse as parseYaml22 } from "yaml";
|
|
@@ -10944,25 +11076,26 @@ import { parse as parseYaml52, stringify as stringifyYaml22 } from "yaml";
|
|
|
10944
11076
|
import { parse as parseYaml42, stringify as stringifyYaml4 } from "yaml";
|
|
10945
11077
|
import matter32 from "gray-matter";
|
|
10946
11078
|
import matter42 from "gray-matter";
|
|
10947
|
-
import { parse as parseYaml72 } from "yaml";
|
|
10948
11079
|
import matter52 from "gray-matter";
|
|
11080
|
+
import { parse as parseYaml72 } from "yaml";
|
|
11081
|
+
import matter62 from "gray-matter";
|
|
10949
11082
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
10950
11083
|
import { dirname as dirname2, join as join22, parse as parse2 } from "path";
|
|
10951
11084
|
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
10952
|
-
import matter62 from "gray-matter";
|
|
10953
11085
|
import matter72 from "gray-matter";
|
|
11086
|
+
import matter82 from "gray-matter";
|
|
10954
11087
|
import { parse as parseYaml82, stringify as stringifyYaml42 } from "yaml";
|
|
10955
11088
|
import fs32 from "fs";
|
|
10956
11089
|
import path32 from "path";
|
|
10957
11090
|
import crypto2 from "crypto";
|
|
10958
|
-
import
|
|
11091
|
+
import matter92 from "gray-matter";
|
|
10959
11092
|
import crypto3 from "crypto";
|
|
10960
11093
|
import fs4 from "fs";
|
|
10961
|
-
import matter92 from "gray-matter";
|
|
10962
11094
|
import matter102 from "gray-matter";
|
|
11095
|
+
import matter112 from "gray-matter";
|
|
10963
11096
|
import { parse as parseYaml92, stringify as stringifyYaml5 } from "yaml";
|
|
10964
11097
|
import { parse as parseYaml102, stringify as stringifyYaml6 } from "yaml";
|
|
10965
|
-
import
|
|
11098
|
+
import matter122 from "gray-matter";
|
|
10966
11099
|
function exists2(p) {
|
|
10967
11100
|
return fs3.existsSync(p);
|
|
10968
11101
|
}
|
|
@@ -11130,7 +11263,7 @@ function parseImpactAnalysis2(data) {
|
|
|
11130
11263
|
}
|
|
11131
11264
|
function parseArtifact2(filePath, raw) {
|
|
11132
11265
|
try {
|
|
11133
|
-
const { data } =
|
|
11266
|
+
const { data } = matter12(raw);
|
|
11134
11267
|
const globs = Array.isArray(data.code) ? data.code.filter(Boolean) : [];
|
|
11135
11268
|
return {
|
|
11136
11269
|
filePath,
|
|
@@ -12684,7 +12817,113 @@ function suggestInitiativesForExternalItem(dir, integration, externalId) {
|
|
|
12684
12817
|
)
|
|
12685
12818
|
);
|
|
12686
12819
|
}
|
|
12820
|
+
var RESOURCE_ARTIFACT_TYPE2 = "project-resource";
|
|
12821
|
+
function str2(v) {
|
|
12822
|
+
return typeof v === "string" && v.trim() ? v.trim() : null;
|
|
12823
|
+
}
|
|
12824
|
+
function strArray3(v) {
|
|
12825
|
+
if (Array.isArray(v)) return v.map((x2) => String(x2).trim()).filter(Boolean);
|
|
12826
|
+
if (typeof v === "string" && v.trim()) return [v.trim()];
|
|
12827
|
+
return [];
|
|
12828
|
+
}
|
|
12829
|
+
function parseInterfaces2(v) {
|
|
12830
|
+
if (!Array.isArray(v)) return [];
|
|
12831
|
+
const out = [];
|
|
12832
|
+
for (const raw of v) {
|
|
12833
|
+
if (!raw || typeof raw !== "object") continue;
|
|
12834
|
+
const o2 = raw;
|
|
12835
|
+
const type = str2(o2.type);
|
|
12836
|
+
if (!type) continue;
|
|
12837
|
+
out.push({
|
|
12838
|
+
type,
|
|
12839
|
+
tool: str2(o2.tool),
|
|
12840
|
+
provider: str2(o2.provider),
|
|
12841
|
+
purpose: str2(o2.purpose),
|
|
12842
|
+
operations: strArray3(o2.operations),
|
|
12843
|
+
environments: strArray3(o2.environments),
|
|
12844
|
+
constraints: str2(o2.constraints)
|
|
12845
|
+
});
|
|
12846
|
+
}
|
|
12847
|
+
return out;
|
|
12848
|
+
}
|
|
12849
|
+
function parseBoundaries2(v) {
|
|
12850
|
+
if (!v || typeof v !== "object" || Array.isArray(v)) return {};
|
|
12851
|
+
const out = {};
|
|
12852
|
+
for (const [env, val] of Object.entries(v)) {
|
|
12853
|
+
const list22 = strArray3(val);
|
|
12854
|
+
if (list22.length > 0) out[env] = list22;
|
|
12855
|
+
}
|
|
12856
|
+
return out;
|
|
12857
|
+
}
|
|
12858
|
+
function parseAuth2(v) {
|
|
12859
|
+
if (!v || typeof v !== "object" || Array.isArray(v)) return { mode: null, refs: [] };
|
|
12860
|
+
const o2 = v;
|
|
12861
|
+
const refs = strArray3(o2.refs).filter((r2) => !r2.includes("=") && /^[A-Za-z0-9_.-]+$/.test(r2));
|
|
12862
|
+
return { mode: str2(o2.mode), refs };
|
|
12863
|
+
}
|
|
12864
|
+
function toDetail2(filePath, relPath, raw) {
|
|
12865
|
+
const parsed = matter42(raw);
|
|
12866
|
+
const fm = parsed.data ?? {};
|
|
12867
|
+
const body = parsed.content.trim();
|
|
12868
|
+
const id = str2(fm.id) ?? "";
|
|
12869
|
+
return {
|
|
12870
|
+
id,
|
|
12871
|
+
title: str2(fm.title) ?? id,
|
|
12872
|
+
resourceType: str2(fm.resource_type),
|
|
12873
|
+
provider: str2(fm.provider),
|
|
12874
|
+
environments: strArray3(fm.environments),
|
|
12875
|
+
path: relPath,
|
|
12876
|
+
purpose: str2(fm.purpose),
|
|
12877
|
+
interfaces: parseInterfaces2(fm.access_interfaces),
|
|
12878
|
+
boundaries: parseBoundaries2(fm.access_boundaries),
|
|
12879
|
+
...(() => {
|
|
12880
|
+
const a2 = parseAuth2(fm.authentication);
|
|
12881
|
+
return { authRefs: a2.refs, authMode: a2.mode };
|
|
12882
|
+
})(),
|
|
12883
|
+
markdownBody: body
|
|
12884
|
+
};
|
|
12885
|
+
}
|
|
12886
|
+
function getResources2(dir) {
|
|
12887
|
+
return discoverKnowledge2(dir).filter((a2) => a2.type === RESOURCE_ARTIFACT_TYPE2).map((a2) => {
|
|
12888
|
+
const fm = a2.rawFrontmatter;
|
|
12889
|
+
return {
|
|
12890
|
+
id: a2.id || str2(fm.id) || a2.title,
|
|
12891
|
+
title: a2.title || str2(fm.title) || a2.id,
|
|
12892
|
+
resourceType: str2(fm.resource_type),
|
|
12893
|
+
provider: str2(fm.provider),
|
|
12894
|
+
environments: strArray3(fm.environments),
|
|
12895
|
+
path: a2.relPath
|
|
12896
|
+
};
|
|
12897
|
+
}).sort((x2, y) => x2.id.localeCompare(y.id));
|
|
12898
|
+
}
|
|
12899
|
+
function getResource2(dir, id) {
|
|
12900
|
+
const match = discoverKnowledge2(dir).find(
|
|
12901
|
+
(a2) => a2.type === RESOURCE_ARTIFACT_TYPE2 && (a2.id === id || a2.title === id)
|
|
12902
|
+
);
|
|
12903
|
+
if (!match) return null;
|
|
12904
|
+
const raw = readFile2(match.filePath);
|
|
12905
|
+
return toDetail2(match.filePath, match.relPath, raw);
|
|
12906
|
+
}
|
|
12907
|
+
function parseWorkItemResources2(fm) {
|
|
12908
|
+
const v = fm.resources;
|
|
12909
|
+
if (!Array.isArray(v)) return [];
|
|
12910
|
+
const out = [];
|
|
12911
|
+
for (const raw of v) {
|
|
12912
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) continue;
|
|
12913
|
+
const o2 = raw;
|
|
12914
|
+
const id = str2(o2.id);
|
|
12915
|
+
if (!id) continue;
|
|
12916
|
+
out.push({ id, role: str2(o2.role) ?? "affected" });
|
|
12917
|
+
}
|
|
12918
|
+
return out;
|
|
12919
|
+
}
|
|
12687
12920
|
var KNOWLEDGE22 = "knowledge";
|
|
12921
|
+
var RESOURCE_ROLE_EDGE2 = {
|
|
12922
|
+
affected: "affects",
|
|
12923
|
+
implementation: "uses",
|
|
12924
|
+
validation: "validates_with",
|
|
12925
|
+
delivery: "delivers_through"
|
|
12926
|
+
};
|
|
12688
12927
|
var ACTIVE_STATUSES2 = ["draft", "ready", "in-progress", "blocked"];
|
|
12689
12928
|
var ALL_STATUSES2 = ["draft", "ready", "in-progress", "blocked", "completed"];
|
|
12690
12929
|
function scopeStatuses2(scope) {
|
|
@@ -12763,6 +13002,16 @@ function buildGraph2(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
|
|
|
12763
13002
|
if (cand.materializedAs) addEdge(candId, `wi:${cand.materializedAs}`, "materialized_as");
|
|
12764
13003
|
}
|
|
12765
13004
|
}
|
|
13005
|
+
const resourceTitleById = /* @__PURE__ */ new Map();
|
|
13006
|
+
for (const r2 of getResources2(dir)) {
|
|
13007
|
+
resourceTitleById.set(r2.id, r2.title);
|
|
13008
|
+
addNode({
|
|
13009
|
+
id: `resource:${r2.id}`,
|
|
13010
|
+
type: "project-resource",
|
|
13011
|
+
label: r2.title ? `${r2.id} ${r2.title}`.trim() : r2.id,
|
|
13012
|
+
path: r2.path
|
|
13013
|
+
});
|
|
13014
|
+
}
|
|
12766
13015
|
for (const wi of selectedWIs) {
|
|
12767
13016
|
const id = wi.id || wi.title;
|
|
12768
13017
|
if (!id || !id.trim()) continue;
|
|
@@ -12787,6 +13036,12 @@ function buildGraph2(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
|
|
|
12787
13036
|
addNode({ id: capId, type: "capability", label: cap });
|
|
12788
13037
|
addEdge(wiNodeId, capId, "implements");
|
|
12789
13038
|
}
|
|
13039
|
+
for (const rel of parseWorkItemResources2(wi.rawFrontmatter)) {
|
|
13040
|
+
const resId = `resource:${rel.id}`;
|
|
13041
|
+
const title = resourceTitleById.get(rel.id);
|
|
13042
|
+
addNode({ id: resId, type: "project-resource", label: title ? `${rel.id} ${title}`.trim() : rel.id });
|
|
13043
|
+
addEdge(wiNodeId, resId, RESOURCE_ROLE_EDGE2[rel.role] ?? "uses");
|
|
13044
|
+
}
|
|
12790
13045
|
for (const dec of wi.decisions) {
|
|
12791
13046
|
if (!dec || !dec.trim()) continue;
|
|
12792
13047
|
const adrId = `adr:${dec}`;
|
|
@@ -13280,8 +13535,13 @@ dependencies.
|
|
|
13280
13535
|
flags, content/copy, authentication/authorization, notifications, analytics, documentation,
|
|
13281
13536
|
operations/release \u2014 as affected, reviewed-not-affected, unknown, or not-applicable.
|
|
13282
13537
|
4. **Module review** \u2014 for multirepo, evaluate each mapped module with the same statuses.
|
|
13283
|
-
5. **
|
|
13284
|
-
|
|
13538
|
+
5. **Resource review** \u2014 ask: does this change need to observe, modify, validate or deliver against a
|
|
13539
|
+
known external resource of the project (a Project Resource under \`knowledge/tech/resources/\` \u2014
|
|
13540
|
+
database, cloud, API, queue, storage\u2026)? If so, declare it in the WI front matter \`resources:\` with
|
|
13541
|
+
a role (\`affected\`, \`implementation\`, \`validation\`, \`delivery\`). Resources are external systems,
|
|
13542
|
+
distinct from \`affected_modules\` (code). Not every WI needs resources (e.g. a CSS change does not).
|
|
13543
|
+
6. **Completeness review** \u2014 confirm: outcome covered, journey covered, modules assessed, resources
|
|
13544
|
+
assessed, unknowns visible, acceptance criteria end-to-end, scope and out-of-scope coherent.
|
|
13285
13545
|
|
|
13286
13546
|
## Rules
|
|
13287
13547
|
|
|
@@ -13292,6 +13552,8 @@ dependencies.
|
|
|
13292
13552
|
- Include at least one end-to-end acceptance criterion for user-facing changes.
|
|
13293
13553
|
- Do not reduce a product intent to the first technical implementation found.
|
|
13294
13554
|
- Evaluate surfaces and modules before proposing files.
|
|
13555
|
+
- Consider Project Resources when the scope justifies it; never invent resources not backed by
|
|
13556
|
+
project knowledge, and keep \`resources\` (external systems) separate from \`affected_modules\` (code).
|
|
13295
13557
|
- This skill refines scope and acceptance criteria but does not approve implementation readiness.
|
|
13296
13558
|
Readiness requires human confirmation through \`kaddo ready\` or the MCP \`mark_work_item_ready\` action.
|
|
13297
13559
|
|
|
@@ -16979,6 +17241,8 @@ function getWorkItem(dir, workItemId) {
|
|
|
16979
17241
|
path: match.relPath,
|
|
16980
17242
|
// Faithful source of truth: the artifact body already read above, not reconstructed (WI-025).
|
|
16981
17243
|
markdownBody: body.trim(),
|
|
17244
|
+
// External systems this WI relates to (WI-030), separate from affectedModules (code).
|
|
17245
|
+
resources: parseWorkItemResources2(fm),
|
|
16982
17246
|
...parseSystemImpact(dir, fm)
|
|
16983
17247
|
};
|
|
16984
17248
|
return { ...detail, refinement: computeRefinementStatus(detail) };
|
|
@@ -17043,7 +17307,7 @@ function parseOriginalSnapshot(fm) {
|
|
|
17043
17307
|
function readBody(filePath) {
|
|
17044
17308
|
try {
|
|
17045
17309
|
const raw = readFile2(filePath);
|
|
17046
|
-
return
|
|
17310
|
+
return matter82(raw).content;
|
|
17047
17311
|
} catch {
|
|
17048
17312
|
return "";
|
|
17049
17313
|
}
|
|
@@ -17319,8 +17583,16 @@ ${body}`);
|
|
|
17319
17583
|
${out.join("\n\n")}
|
|
17320
17584
|
`.replace(/\n{3,}/g, "\n\n");
|
|
17321
17585
|
}
|
|
17586
|
+
function ensureH1(body, title) {
|
|
17587
|
+
const trimmed = body.trim();
|
|
17588
|
+
const firstMeaningful = trimmed.split(/\r?\n/).find((l2) => l2.trim());
|
|
17589
|
+
if (firstMeaningful && /^#\s+/.test(firstMeaningful.trim())) return trimmed;
|
|
17590
|
+
return `# ${title}
|
|
17591
|
+
|
|
17592
|
+
${trimmed}`;
|
|
17593
|
+
}
|
|
17322
17594
|
function serialize(data, body) {
|
|
17323
|
-
return
|
|
17595
|
+
return matter92.stringify(`
|
|
17324
17596
|
${body.trim()}
|
|
17325
17597
|
`, data);
|
|
17326
17598
|
}
|
|
@@ -17354,6 +17626,7 @@ function createWorkItem(dir, opts) {
|
|
|
17354
17626
|
const id = nextWorkItemId(dir);
|
|
17355
17627
|
const title = intent.split(/\r?\n/)[0].trim().slice(0, 120);
|
|
17356
17628
|
const today22 = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
17629
|
+
const summary = opts.summary?.trim() || intent;
|
|
17357
17630
|
const source = opts.source ? { ...opts.source, inferred: false } : { type: "manual", inferred: false };
|
|
17358
17631
|
const data = {
|
|
17359
17632
|
type,
|
|
@@ -17365,15 +17638,16 @@ function createWorkItem(dir, opts) {
|
|
|
17365
17638
|
source,
|
|
17366
17639
|
generated_by: "kaddo-admin",
|
|
17367
17640
|
affected_modules: [],
|
|
17368
|
-
summary
|
|
17641
|
+
summary
|
|
17369
17642
|
};
|
|
17370
17643
|
if (opts.originalSnapshot) data.original_snapshot = opts.originalSnapshot;
|
|
17371
17644
|
const answers = opts.answers ?? {};
|
|
17372
17645
|
const hasAnswers = Object.values(answers).some((v) => v?.trim());
|
|
17373
|
-
const
|
|
17646
|
+
const providedBody = opts.body?.trim();
|
|
17647
|
+
const body = providedBody ? ensureH1(providedBody, title) : hasAnswers ? captureBody(title, type, answers) : freshBody({
|
|
17374
17648
|
title,
|
|
17375
17649
|
type,
|
|
17376
|
-
summary
|
|
17650
|
+
summary,
|
|
17377
17651
|
scopeUnknowns: [],
|
|
17378
17652
|
affectedModules: [],
|
|
17379
17653
|
moduleCoverage: [],
|
|
@@ -17712,6 +17986,7 @@ function buildImplementationHandoff(dir, workItemId) {
|
|
|
17712
17986
|
const affectedModules = match.affectedModules;
|
|
17713
17987
|
const domains = match.domains;
|
|
17714
17988
|
const scopeConfidence = match.scopeConfidence;
|
|
17989
|
+
const wiResources = parseWorkItemResources2(match.rawFrontmatter);
|
|
17715
17990
|
const lines = [
|
|
17716
17991
|
`Implement Work Item ${wiId} \u2014 "${wiTitle}" \u2014 in project "${projectName}" using Kaddo.`,
|
|
17717
17992
|
"",
|
|
@@ -17801,6 +18076,41 @@ function buildImplementationHandoff(dir, workItemId) {
|
|
|
17801
18076
|
"Do not copy all legacy knowledge \u2014 assemble proportionally to scope."
|
|
17802
18077
|
);
|
|
17803
18078
|
}
|
|
18079
|
+
if (wiResources.length > 0) {
|
|
18080
|
+
lines.push(
|
|
18081
|
+
"",
|
|
18082
|
+
"--- Relevant Resources ---",
|
|
18083
|
+
"",
|
|
18084
|
+
"This Work Item relates to external project resources. The handoff INFORMS you about them \u2014 it",
|
|
18085
|
+
"never connects to them, runs any interface, or reveals credential values. Respect each boundary."
|
|
18086
|
+
);
|
|
18087
|
+
for (const rel of wiResources) {
|
|
18088
|
+
const res = getResource2(dir, rel.id);
|
|
18089
|
+
if (!res) {
|
|
18090
|
+
lines.push("", `- ${rel.id} (role: ${rel.role}) \u2014 definition not found under knowledge/tech/resources/.`);
|
|
18091
|
+
continue;
|
|
18092
|
+
}
|
|
18093
|
+
lines.push("", `- ${res.id} \u2014 ${res.title} (role: ${rel.role})`);
|
|
18094
|
+
if (res.resourceType || res.provider) {
|
|
18095
|
+
lines.push(` type: ${res.resourceType ?? "\u2014"}${res.provider ? ` \xB7 provider: ${res.provider}` : ""}`);
|
|
18096
|
+
}
|
|
18097
|
+
if (res.purpose) lines.push(` purpose: ${res.purpose}`);
|
|
18098
|
+
if (res.environments.length > 0) lines.push(` environments: ${res.environments.join(", ")}`);
|
|
18099
|
+
if (res.interfaces.length > 0) {
|
|
18100
|
+
const ifaces = res.interfaces.map((i) => i.tool || i.provider ? `${i.type} (${i.tool ?? i.provider})` : i.type);
|
|
18101
|
+
lines.push(` interfaces: ${ifaces.join(", ")}`);
|
|
18102
|
+
}
|
|
18103
|
+
for (const [env, ops] of Object.entries(res.boundaries)) {
|
|
18104
|
+
lines.push(` boundary (${env}): ${ops.join(", ")}`);
|
|
18105
|
+
}
|
|
18106
|
+
if (res.authRefs.length > 0) lines.push(` auth references (names only): ${res.authRefs.join(", ")}`);
|
|
18107
|
+
}
|
|
18108
|
+
lines.push(
|
|
18109
|
+
"",
|
|
18110
|
+
"Treat these as knowledge, not availability: an interface being declared does not mean it is",
|
|
18111
|
+
"installed or connected. Do not execute any interface or migration without explicit human confirmation."
|
|
18112
|
+
);
|
|
18113
|
+
}
|
|
17804
18114
|
lines.push(
|
|
17805
18115
|
"",
|
|
17806
18116
|
"--- Implementation Plan ---",
|
|
@@ -17821,6 +18131,7 @@ function buildImplementationHandoff(dir, workItemId) {
|
|
|
17821
18131
|
projectName,
|
|
17822
18132
|
lifecycle,
|
|
17823
18133
|
affectedModules,
|
|
18134
|
+
resources: wiResources,
|
|
17824
18135
|
domains,
|
|
17825
18136
|
scopeConfidence,
|
|
17826
18137
|
hasUnmaterializedDecisions: unmaterialized,
|
|
@@ -18072,7 +18383,7 @@ function detectFormat(raw) {
|
|
|
18072
18383
|
if (!trimmed) throw new ImportError("EMPTY_INPUT", "Import content is empty.");
|
|
18073
18384
|
if (trimmed.startsWith("---")) {
|
|
18074
18385
|
try {
|
|
18075
|
-
const { data } =
|
|
18386
|
+
const { data } = matter102(trimmed);
|
|
18076
18387
|
if (data && typeof data === "object" && Object.keys(data).length > 0) {
|
|
18077
18388
|
const d3 = data;
|
|
18078
18389
|
if (d3.type === "work-item" || d3.id && typeof d3.id === "string" && /^WI-\d+/.test(d3.id)) {
|
|
@@ -18094,7 +18405,7 @@ function parseContent(raw) {
|
|
|
18094
18405
|
let bodyText = trimmed;
|
|
18095
18406
|
if (format === "kaddo-frontmatter" || format === "markdown-frontmatter") {
|
|
18096
18407
|
try {
|
|
18097
|
-
const parsed =
|
|
18408
|
+
const parsed = matter102(trimmed);
|
|
18098
18409
|
frontmatterFields = parsed.data ?? {};
|
|
18099
18410
|
bodyText = parsed.content.trim();
|
|
18100
18411
|
} catch {
|
|
@@ -18151,15 +18462,21 @@ function extractSections(body) {
|
|
|
18151
18462
|
flush();
|
|
18152
18463
|
return sections;
|
|
18153
18464
|
}
|
|
18465
|
+
function deriveShortSummary(parsed) {
|
|
18466
|
+
const paragraph = parsed.body.split(/\n{2,}/).map((p) => p.trim()).find((p) => p && !/^#{1,6}\s/.test(p) && !/^>/.test(p) && p !== parsed.title);
|
|
18467
|
+
const base = paragraph ? paragraph.replace(/\s+/g, " ") : parsed.title;
|
|
18468
|
+
return base.length > 280 ? `${base.slice(0, 277).trimEnd()}\u2026` : base;
|
|
18469
|
+
}
|
|
18154
18470
|
function normalizeImportFields(parsed, opts) {
|
|
18155
18471
|
const discardedFields = [];
|
|
18156
18472
|
for (const key of Object.keys(parsed.candidateFields)) {
|
|
18157
18473
|
if (LIFECYCLE_FIELDS.has(key)) discardedFields.push(key);
|
|
18158
18474
|
}
|
|
18159
18475
|
const type = opts.userType ? normalizeType(opts.userType) ?? "feature" : parsed.type ?? "feature";
|
|
18160
|
-
const
|
|
18161
|
-
|
|
18162
|
-
|
|
18476
|
+
const hasRealBody = !!parsed.body && parsed.body !== parsed.title;
|
|
18477
|
+
const intent = parsed.title;
|
|
18478
|
+
const body = hasRealBody ? parsed.body : void 0;
|
|
18479
|
+
const summary = hasRealBody ? deriveShortSummary(parsed) : parsed.title;
|
|
18163
18480
|
const source = {
|
|
18164
18481
|
type: opts.importSource === "chat" ? "chat" : "external",
|
|
18165
18482
|
imported_at: (/* @__PURE__ */ new Date()).toISOString().split("T")[0]
|
|
@@ -18167,14 +18484,14 @@ ${parsed.body}` : parsed.title;
|
|
|
18167
18484
|
source.source_format = opts.sourceFormat;
|
|
18168
18485
|
source.source_hash = opts.sourceHash;
|
|
18169
18486
|
const snapshot = { title: parsed.title };
|
|
18170
|
-
if (
|
|
18487
|
+
if (summary !== parsed.title) snapshot.description = summary;
|
|
18171
18488
|
if (typeof parsed.candidateFields.type === "string") snapshot.type = parsed.candidateFields.type;
|
|
18172
18489
|
if (typeof parsed.candidateFields.status === "string") snapshot.status = parsed.candidateFields.status;
|
|
18173
18490
|
if (Array.isArray(parsed.candidateFields.labels)) {
|
|
18174
18491
|
const labels = parsed.candidateFields.labels.filter((l2) => typeof l2 === "string");
|
|
18175
18492
|
if (labels.length > 0) snapshot.labels = labels;
|
|
18176
18493
|
}
|
|
18177
|
-
return { type, intent, source, snapshot, discardedFields };
|
|
18494
|
+
return { type, intent, summary, body, source, snapshot, discardedFields };
|
|
18178
18495
|
}
|
|
18179
18496
|
function computeContentHash(raw) {
|
|
18180
18497
|
return crypto3.createHash("sha256").update(raw.trim(), "utf-8").digest("hex");
|
|
@@ -18185,7 +18502,7 @@ function findDuplicateByHash(dir, hash) {
|
|
|
18185
18502
|
const raw = readFile2(item.filePath);
|
|
18186
18503
|
if (!raw) continue;
|
|
18187
18504
|
try {
|
|
18188
|
-
const { data } =
|
|
18505
|
+
const { data } = matter102(raw);
|
|
18189
18506
|
const source = data?.source;
|
|
18190
18507
|
if (source && typeof source === "object" && !Array.isArray(source)) {
|
|
18191
18508
|
const s = source;
|
|
@@ -18260,6 +18577,8 @@ function importWorkItem(dir, opts) {
|
|
|
18260
18577
|
const result = createWorkItem(dir, {
|
|
18261
18578
|
intent: normalized.intent,
|
|
18262
18579
|
type: normalized.type,
|
|
18580
|
+
summary: normalized.summary,
|
|
18581
|
+
body: normalized.body,
|
|
18263
18582
|
source: normalized.source,
|
|
18264
18583
|
originalSnapshot: normalized.snapshot
|
|
18265
18584
|
});
|
|
@@ -19078,7 +19397,7 @@ function buildInitiativeContent(id, input) {
|
|
|
19078
19397
|
if (input.priority) data.priority = input.priority;
|
|
19079
19398
|
if (input.source) data.source = input.source;
|
|
19080
19399
|
if (input.sourceId) data.source_id = input.sourceId;
|
|
19081
|
-
return
|
|
19400
|
+
return matter122.stringify("\n" + (input.body ?? defaultBody(input.title.trim())), data);
|
|
19082
19401
|
}
|
|
19083
19402
|
function createInitiative(dir, input) {
|
|
19084
19403
|
if (!input.title?.trim()) throw new InitiativeWriteError("Initiative title is required.");
|
|
@@ -19102,10 +19421,10 @@ function transitionInitiative(dir, id, to) {
|
|
|
19102
19421
|
throw new InitiativeWriteError(`Invalid transition ${ini.status} \u2192 ${to}.`);
|
|
19103
19422
|
}
|
|
19104
19423
|
const raw = readFile2(ini.filePath);
|
|
19105
|
-
const { data, content } =
|
|
19424
|
+
const { data, content } = matter122(raw, {});
|
|
19106
19425
|
data.status = to;
|
|
19107
19426
|
if (to === "completed") data.completed_at = today2();
|
|
19108
|
-
const newContent =
|
|
19427
|
+
const newContent = matter122.stringify(content, data);
|
|
19109
19428
|
writeFile2(ini.filePath, newContent);
|
|
19110
19429
|
const updated = parseInitiative2(dir, ini.filePath, newContent);
|
|
19111
19430
|
if (!updated) throw new InitiativeWriteError("Failed to parse the Initiative after transition.");
|
|
@@ -19115,7 +19434,7 @@ function addCandidate(dir, id, candidate) {
|
|
|
19115
19434
|
const ini = getInitiative(dir, id);
|
|
19116
19435
|
if (!ini) throw new InitiativeWriteError(`Initiative ${id} not found.`);
|
|
19117
19436
|
const raw = readFile2(ini.filePath);
|
|
19118
|
-
const { data, content } =
|
|
19437
|
+
const { data, content } = matter122(raw, {});
|
|
19119
19438
|
const existing = Array.isArray(data.candidates) ? data.candidates : [];
|
|
19120
19439
|
const fallbackId = `WI-CANDIDATE-${String(existing.length + 1).padStart(3, "0")}`;
|
|
19121
19440
|
const normalized = normalizeCandidate(candidate, fallbackId);
|
|
@@ -19123,7 +19442,7 @@ function addCandidate(dir, id, candidate) {
|
|
|
19123
19442
|
throw new InitiativeWriteError(`Candidate ${normalized.id} already exists in ${id}.`);
|
|
19124
19443
|
}
|
|
19125
19444
|
data.candidates = [...existing, candidateToData(normalized)];
|
|
19126
|
-
const newContent =
|
|
19445
|
+
const newContent = matter122.stringify(content, data);
|
|
19127
19446
|
writeFile2(ini.filePath, newContent);
|
|
19128
19447
|
const updated = parseInitiative2(dir, ini.filePath, newContent);
|
|
19129
19448
|
if (!updated) throw new InitiativeWriteError("Failed to parse the Initiative after adding a candidate.");
|
|
@@ -19133,7 +19452,7 @@ function updateInitiative(dir, id, patch) {
|
|
|
19133
19452
|
const ini = getInitiative(dir, id);
|
|
19134
19453
|
if (!ini) throw new InitiativeWriteError(`Initiative ${id} not found.`);
|
|
19135
19454
|
const raw = readFile2(ini.filePath);
|
|
19136
|
-
const { data, content } =
|
|
19455
|
+
const { data, content } = matter122(raw, {});
|
|
19137
19456
|
if (patch.title !== void 0) {
|
|
19138
19457
|
if (!patch.title.trim()) throw new InitiativeWriteError("Initiative title cannot be empty.");
|
|
19139
19458
|
data.title = patch.title.trim();
|
|
@@ -19143,7 +19462,7 @@ function updateInitiative(dir, id, patch) {
|
|
|
19143
19462
|
if (patch.knowledgeLevel !== void 0) data.knowledge_level = patch.knowledgeLevel;
|
|
19144
19463
|
if (patch.domains !== void 0) data.domains = patch.domains;
|
|
19145
19464
|
if (patch.relatedCapabilities !== void 0) data.related_capabilities = patch.relatedCapabilities;
|
|
19146
|
-
const newContent =
|
|
19465
|
+
const newContent = matter122.stringify(content, data);
|
|
19147
19466
|
writeFile2(ini.filePath, newContent);
|
|
19148
19467
|
const updated = parseInitiative2(dir, ini.filePath, newContent);
|
|
19149
19468
|
if (!updated) throw new InitiativeWriteError("Failed to parse the Initiative after update.");
|
|
@@ -19161,10 +19480,10 @@ function addExternalLink(dir, id, link) {
|
|
|
19161
19480
|
throw new InitiativeWriteError(`External link ${link.integration}:${link.externalId} already exists on ${id}.`);
|
|
19162
19481
|
}
|
|
19163
19482
|
const raw = readFile2(ini.filePath);
|
|
19164
|
-
const { data, content } =
|
|
19483
|
+
const { data, content } = matter122(raw, {});
|
|
19165
19484
|
const existing = Array.isArray(data.external_links) ? data.external_links : [];
|
|
19166
19485
|
data.external_links = [...existing, externalLinkToData(link)];
|
|
19167
|
-
const newContent =
|
|
19486
|
+
const newContent = matter122.stringify(content, data);
|
|
19168
19487
|
writeFile2(ini.filePath, newContent);
|
|
19169
19488
|
const updated = parseInitiative2(dir, ini.filePath, newContent);
|
|
19170
19489
|
if (!updated) throw new InitiativeWriteError("Failed to parse the Initiative after adding an external link.");
|
|
@@ -19233,7 +19552,7 @@ function buildWorkItemContent(wiId, iniId, candidate) {
|
|
|
19233
19552
|
"_What did we learn? Update after completion._",
|
|
19234
19553
|
""
|
|
19235
19554
|
].join("\n");
|
|
19236
|
-
return
|
|
19555
|
+
return matter122.stringify("\n" + body, data);
|
|
19237
19556
|
}
|
|
19238
19557
|
function materializeCandidate(dir, id, candidateId) {
|
|
19239
19558
|
const ini = getInitiative(dir, id);
|
|
@@ -19250,13 +19569,13 @@ function materializeCandidate(dir, id, candidateId) {
|
|
|
19250
19569
|
ensureDir2(join3(dir, DRAFT_DIR));
|
|
19251
19570
|
writeFile2(join3(dir, DRAFT_DIR, fileName), buildWorkItemContent(wiId, ini.id, candidate));
|
|
19252
19571
|
const raw = readFile2(ini.filePath);
|
|
19253
|
-
const { data, content } =
|
|
19572
|
+
const { data, content } = matter122(raw, {});
|
|
19254
19573
|
const cands = Array.isArray(data.candidates) ? data.candidates : [];
|
|
19255
19574
|
for (const c of cands) {
|
|
19256
19575
|
if (c && typeof c === "object" && String(c.id) === candidateId) c.materialized_as = wiId;
|
|
19257
19576
|
}
|
|
19258
19577
|
data.candidates = cands;
|
|
19259
|
-
const newContent =
|
|
19578
|
+
const newContent = matter122.stringify(content, data);
|
|
19260
19579
|
writeFile2(ini.filePath, newContent);
|
|
19261
19580
|
const updated = parseInitiative2(dir, ini.filePath, newContent);
|
|
19262
19581
|
if (!updated) throw new InitiativeWriteError("Failed to parse the Initiative after materialization.");
|
|
@@ -19462,12 +19781,12 @@ function markWorkItemReady(root, id, confirm2) {
|
|
|
19462
19781
|
}
|
|
19463
19782
|
const raw = readText(root, item.path);
|
|
19464
19783
|
if (!raw) return fail(`Could not read Work Item file: ${item.path}`);
|
|
19465
|
-
const parsed =
|
|
19784
|
+
const parsed = matter13(raw);
|
|
19466
19785
|
const data = { ...parsed.data };
|
|
19467
19786
|
const today3 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
19468
19787
|
data.status = "ready";
|
|
19469
19788
|
data.ready_at = today3;
|
|
19470
|
-
const newContent =
|
|
19789
|
+
const newContent = matter13.stringify(parsed.content, data);
|
|
19471
19790
|
const posixPath = item.path.replace(/\\/g, "/");
|
|
19472
19791
|
let newRelPath = item.path;
|
|
19473
19792
|
if (posixPath.includes("/work-items/draft/")) {
|
|
@@ -19630,11 +19949,19 @@ function setTelemetryConsentTool(root, args) {
|
|
|
19630
19949
|
persistConsent(root, consent);
|
|
19631
19950
|
return ok({ applied: true, consent, message: `Telemetry ${consent}.` });
|
|
19632
19951
|
}
|
|
19952
|
+
function listResourcesTool(root) {
|
|
19953
|
+
return ok(getResources(root));
|
|
19954
|
+
}
|
|
19955
|
+
function getResourceTool(root, id) {
|
|
19956
|
+
const resource = getResource(root, id);
|
|
19957
|
+
if (!resource) return fail(`Project Resource "${id}" not found.`);
|
|
19958
|
+
return ok(resource);
|
|
19959
|
+
}
|
|
19633
19960
|
|
|
19634
19961
|
// src/multirepo.ts
|
|
19635
19962
|
import fs5 from "fs";
|
|
19636
19963
|
import path4 from "path";
|
|
19637
|
-
import
|
|
19964
|
+
import matter14 from "gray-matter";
|
|
19638
19965
|
import { parse as parseYaml11, stringify as stringifyYaml7 } from "yaml";
|
|
19639
19966
|
var ok2 = (data) => ({ ok: true, data });
|
|
19640
19967
|
var fail2 = (message) => ({ ok: false, message });
|
|
@@ -19786,7 +20113,7 @@ function validateWorkItemModulesTool(root, args) {
|
|
|
19786
20113
|
if (!wi) return fail2(`Work Item "${args.workItemId}" not found.`);
|
|
19787
20114
|
const raw = readText(root, wi.path);
|
|
19788
20115
|
if (!raw) return fail2(`Could not read Work Item file.`);
|
|
19789
|
-
const parsed =
|
|
20116
|
+
const parsed = matter14(raw);
|
|
19790
20117
|
const data = parsed.data;
|
|
19791
20118
|
const affectedModules = Array.isArray(data.affected_modules) ? data.affected_modules.map(String) : [];
|
|
19792
20119
|
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) : [] })) : [];
|
|
@@ -19839,7 +20166,7 @@ function getWorkItemContextTool(root, args) {
|
|
|
19839
20166
|
if (!wi) return fail2(`Work Item "${args.workItemId}" not found.`);
|
|
19840
20167
|
const raw = readText(root, wi.path);
|
|
19841
20168
|
if (!raw) return fail2(`Could not read Work Item file.`);
|
|
19842
|
-
const parsed =
|
|
20169
|
+
const parsed = matter14(raw);
|
|
19843
20170
|
const data = parsed.data;
|
|
19844
20171
|
const affectedModules = Array.isArray(data.affected_modules) ? data.affected_modules.map(String) : [];
|
|
19845
20172
|
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) : [] })) : [];
|
|
@@ -19925,7 +20252,7 @@ function suggestBranchStrategyTool(root, args) {
|
|
|
19925
20252
|
if (!wi) return fail2(`Work Item "${args.workItemId}" not found.`);
|
|
19926
20253
|
const raw = readText(root, wi.path);
|
|
19927
20254
|
if (!raw) return fail2(`Could not read Work Item file.`);
|
|
19928
|
-
const parsed =
|
|
20255
|
+
const parsed = matter14(raw);
|
|
19929
20256
|
const data = parsed.data;
|
|
19930
20257
|
const affectedModules = Array.isArray(data.affected_modules) ? data.affected_modules.map(String) : [];
|
|
19931
20258
|
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) : [] })) : [];
|
|
@@ -20685,14 +21012,14 @@ function systemImpactCandidatesTool(root, args) {
|
|
|
20685
21012
|
}
|
|
20686
21013
|
|
|
20687
21014
|
// ../cli/src/services/integrations.ts
|
|
20688
|
-
import
|
|
21015
|
+
import matter16 from "gray-matter";
|
|
20689
21016
|
import { parse as parseYaml13, stringify as stringifyYaml9 } from "yaml";
|
|
20690
21017
|
|
|
20691
21018
|
// ../cli/src/core/work-item-write.ts
|
|
20692
21019
|
import fs7 from "fs";
|
|
20693
21020
|
import path6 from "path";
|
|
20694
21021
|
import crypto4 from "crypto";
|
|
20695
|
-
import
|
|
21022
|
+
import matter15 from "gray-matter";
|
|
20696
21023
|
var SECTION_ORDER2 = [
|
|
20697
21024
|
"Actor",
|
|
20698
21025
|
"Outcome",
|
|
@@ -21740,7 +22067,7 @@ async function integrationsWorkItemTool(root, args) {
|
|
|
21740
22067
|
}
|
|
21741
22068
|
|
|
21742
22069
|
// ../cli/src/core/context-pack.ts
|
|
21743
|
-
import
|
|
22070
|
+
import matter17 from "gray-matter";
|
|
21744
22071
|
var CONTEXT_PACK_VERSION = "1";
|
|
21745
22072
|
var ARCH_DIR4 = "knowledge";
|
|
21746
22073
|
function readScanJson(dir) {
|
|
@@ -21753,7 +22080,7 @@ function readScanJson(dir) {
|
|
|
21753
22080
|
}
|
|
21754
22081
|
}
|
|
21755
22082
|
function firstParagraph4(markdown) {
|
|
21756
|
-
const body =
|
|
22083
|
+
const body = matter17(markdown).content.trim();
|
|
21757
22084
|
const para = body.split("\n\n").map((p) => p.trim()).find((p) => p && !p.startsWith("#"));
|
|
21758
22085
|
return para ?? "";
|
|
21759
22086
|
}
|
|
@@ -21807,6 +22134,8 @@ function toContextWorkItem(a2) {
|
|
|
21807
22134
|
wi.validationStatus = a2.validationStatus || (isHistorical ? "not-assessed" : void 0);
|
|
21808
22135
|
wi.releaseStatus = a2.releaseStatus || (isHistorical ? "not-assessed" : void 0);
|
|
21809
22136
|
if (a2.affectedModules.length > 0) wi.affectedModules = a2.affectedModules;
|
|
22137
|
+
const wiResources = parseWorkItemResources(a2.rawFrontmatter);
|
|
22138
|
+
if (wiResources.length > 0) wi.resources = wiResources;
|
|
21810
22139
|
if (a2.scopeConfidence) wi.scopeConfidence = a2.scopeConfidence;
|
|
21811
22140
|
if (a2.moduleCoverage) wi.moduleCoverage = a2.moduleCoverage;
|
|
21812
22141
|
if (a2.impactAnalysis) wi.impactAnalysis = a2.impactAnalysis;
|
|
@@ -23139,6 +23468,16 @@ function createServer(root) {
|
|
|
23139
23468
|
},
|
|
23140
23469
|
async (args) => toolText(guarded(root, () => markWorkItemReady(root, args.id, args.confirm)))
|
|
23141
23470
|
);
|
|
23471
|
+
server.registerTool(
|
|
23472
|
+
"kaddo_list_resources",
|
|
23473
|
+
{ title: "List Project Resources", description: "List external systems the project uses (databases, cloud, APIs\u2026), read-only, no secrets.", inputSchema: {} },
|
|
23474
|
+
async () => toolText(guarded(root, () => listResourcesTool(root)))
|
|
23475
|
+
);
|
|
23476
|
+
server.registerTool(
|
|
23477
|
+
"kaddo_get_resource",
|
|
23478
|
+
{ 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() } },
|
|
23479
|
+
async (args) => toolText(guarded(root, () => getResourceTool(root, args.id)))
|
|
23480
|
+
);
|
|
23142
23481
|
server.registerTool(
|
|
23143
23482
|
"kaddo_list_capsules",
|
|
23144
23483
|
{ title: "List Knowledge Capsules", description: "List registered external Knowledge Capsules.", inputSchema: {} },
|
package/package.json
CHANGED