@kaddo/mcp 3.130.0 → 3.131.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 +1257 -583
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -391,7 +391,7 @@ function join(...parts) {
|
|
|
391
391
|
}
|
|
392
392
|
|
|
393
393
|
// ../cli/src/core/impact-report.ts
|
|
394
|
-
import
|
|
394
|
+
import matter13 from "gray-matter";
|
|
395
395
|
|
|
396
396
|
// ../cli/src/core/config.ts
|
|
397
397
|
import { z } from "zod";
|
|
@@ -2599,6 +2599,9 @@ function parseInitiative(dir, filePath, raw) {
|
|
|
2599
2599
|
source: optStr(data.source) ?? null,
|
|
2600
2600
|
sourceId: optStr(data.source_id) ?? null,
|
|
2601
2601
|
createdAt: optStr(data.created_at) ?? null,
|
|
2602
|
+
epic: optStr(data.epic) ?? null,
|
|
2603
|
+
owners: strList(data.owners),
|
|
2604
|
+
contributors: strList(data.contributors),
|
|
2602
2605
|
externalLinks: parseExternalLinks(data),
|
|
2603
2606
|
candidates: parseCandidates(data),
|
|
2604
2607
|
filePath,
|
|
@@ -2621,8 +2624,123 @@ function discoverInitiatives(dir) {
|
|
|
2621
2624
|
return out.sort((a2, b2) => a2.id.localeCompare(b2.id));
|
|
2622
2625
|
}
|
|
2623
2626
|
|
|
2624
|
-
// ../cli/src/core/
|
|
2627
|
+
// ../cli/src/core/delivery-planning.ts
|
|
2625
2628
|
import matter6 from "gray-matter";
|
|
2629
|
+
var DELIVERY_PLANS_DIR = "knowledge/delivery/plans";
|
|
2630
|
+
var EPICS_DIR = "knowledge/delivery/epics";
|
|
2631
|
+
var PLAN_STATES = ["draft", "in-progress", "completed", "cancelled"];
|
|
2632
|
+
var EPIC_STATES = ["planned", "in-progress", "completed", "cancelled"];
|
|
2633
|
+
function toPosix4(p) {
|
|
2634
|
+
return p.replace(/\\/g, "/");
|
|
2635
|
+
}
|
|
2636
|
+
function optStr2(v) {
|
|
2637
|
+
if (typeof v === "string" && v.trim()) return v.trim();
|
|
2638
|
+
if (typeof v === "number") return String(v);
|
|
2639
|
+
return void 0;
|
|
2640
|
+
}
|
|
2641
|
+
function strList2(v) {
|
|
2642
|
+
if (!Array.isArray(v)) return [];
|
|
2643
|
+
return v.map((x2) => typeof x2 === "string" ? x2.trim() : String(x2)).filter(Boolean);
|
|
2644
|
+
}
|
|
2645
|
+
function relFrom(dir, filePath) {
|
|
2646
|
+
const d3 = toPosix4(dir);
|
|
2647
|
+
const f3 = toPosix4(filePath);
|
|
2648
|
+
return f3.startsWith(d3 + "/") ? f3.slice(d3.length + 1) : f3;
|
|
2649
|
+
}
|
|
2650
|
+
function parseMilestones(data) {
|
|
2651
|
+
const raw = data.milestones;
|
|
2652
|
+
if (!Array.isArray(raw)) return [];
|
|
2653
|
+
const out = [];
|
|
2654
|
+
for (const entry of raw) {
|
|
2655
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
|
|
2656
|
+
const o2 = entry;
|
|
2657
|
+
const id = optStr2(o2.id);
|
|
2658
|
+
if (!id) continue;
|
|
2659
|
+
out.push({ id, title: optStr2(o2.title) ?? id, exitCriteria: strList2(o2.exit_criteria) });
|
|
2660
|
+
}
|
|
2661
|
+
return out;
|
|
2662
|
+
}
|
|
2663
|
+
function parseDeliveryPlan(dir, filePath, raw) {
|
|
2664
|
+
let parsed;
|
|
2665
|
+
try {
|
|
2666
|
+
parsed = matter6(raw, {});
|
|
2667
|
+
} catch {
|
|
2668
|
+
return null;
|
|
2669
|
+
}
|
|
2670
|
+
const data = parsed.data;
|
|
2671
|
+
if (optStr2(data.type) !== "delivery-plan") return null;
|
|
2672
|
+
const id = optStr2(data.id);
|
|
2673
|
+
if (!id) return null;
|
|
2674
|
+
const statusRaw = optStr2(data.status) ?? "draft";
|
|
2675
|
+
const status = PLAN_STATES.includes(statusRaw) ? statusRaw : "draft";
|
|
2676
|
+
return {
|
|
2677
|
+
id,
|
|
2678
|
+
title: optStr2(data.title) ?? id,
|
|
2679
|
+
status,
|
|
2680
|
+
objective: optStr2(data.objective) ?? null,
|
|
2681
|
+
milestones: parseMilestones(data),
|
|
2682
|
+
dependsOn: strList2(data.depends_on),
|
|
2683
|
+
successCriteria: strList2(data.success_criteria),
|
|
2684
|
+
risks: strList2(data.risks),
|
|
2685
|
+
references: strList2(data.references),
|
|
2686
|
+
filePath,
|
|
2687
|
+
relPath: relFrom(dir, filePath),
|
|
2688
|
+
body: parsed.content.trim(),
|
|
2689
|
+
rawFrontmatter: data
|
|
2690
|
+
};
|
|
2691
|
+
}
|
|
2692
|
+
function parseEpic(dir, filePath, raw) {
|
|
2693
|
+
let parsed;
|
|
2694
|
+
try {
|
|
2695
|
+
parsed = matter6(raw, {});
|
|
2696
|
+
} catch {
|
|
2697
|
+
return null;
|
|
2698
|
+
}
|
|
2699
|
+
const data = parsed.data;
|
|
2700
|
+
if (optStr2(data.type) !== "epic") return null;
|
|
2701
|
+
const id = optStr2(data.id);
|
|
2702
|
+
if (!id) return null;
|
|
2703
|
+
const statusRaw = optStr2(data.status) ?? "planned";
|
|
2704
|
+
const status = EPIC_STATES.includes(statusRaw) ? statusRaw : "planned";
|
|
2705
|
+
return {
|
|
2706
|
+
id,
|
|
2707
|
+
title: optStr2(data.title) ?? id,
|
|
2708
|
+
status,
|
|
2709
|
+
outcome: optStr2(data.outcome ?? data.expected_outcome) ?? null,
|
|
2710
|
+
plan: optStr2(data.plan) ?? null,
|
|
2711
|
+
milestone: optStr2(data.milestone) ?? null,
|
|
2712
|
+
dependsOn: strList2(data.depends_on),
|
|
2713
|
+
successCriteria: strList2(data.success_criteria),
|
|
2714
|
+
relatedCapabilities: strList2(data.related_capabilities),
|
|
2715
|
+
decisions: strList2(data.decisions),
|
|
2716
|
+
filePath,
|
|
2717
|
+
relPath: relFrom(dir, filePath),
|
|
2718
|
+
body: parsed.content.trim(),
|
|
2719
|
+
rawFrontmatter: data
|
|
2720
|
+
};
|
|
2721
|
+
}
|
|
2722
|
+
function discoverIn(dir, rel, parse3) {
|
|
2723
|
+
const d3 = join(dir, rel);
|
|
2724
|
+
if (!exists(d3)) return [];
|
|
2725
|
+
const out = [];
|
|
2726
|
+
for (const entry of readDir(d3)) {
|
|
2727
|
+
if (!entry.endsWith(".md")) continue;
|
|
2728
|
+
const full = join(d3, entry);
|
|
2729
|
+
if (!isFile(full)) continue;
|
|
2730
|
+
const parsed = parse3(dir, full, readFile(full));
|
|
2731
|
+
if (parsed) out.push(parsed);
|
|
2732
|
+
}
|
|
2733
|
+
return out;
|
|
2734
|
+
}
|
|
2735
|
+
function discoverDeliveryPlans(dir) {
|
|
2736
|
+
return discoverIn(dir, DELIVERY_PLANS_DIR, parseDeliveryPlan).sort((a2, b2) => a2.id.localeCompare(b2.id));
|
|
2737
|
+
}
|
|
2738
|
+
function discoverEpics(dir) {
|
|
2739
|
+
return discoverIn(dir, EPICS_DIR, parseEpic).sort((a2, b2) => a2.id.localeCompare(b2.id));
|
|
2740
|
+
}
|
|
2741
|
+
|
|
2742
|
+
// ../cli/src/core/resources.ts
|
|
2743
|
+
import matter7 from "gray-matter";
|
|
2626
2744
|
var RESOURCE_ARTIFACT_TYPE = "project-resource";
|
|
2627
2745
|
var RESOURCE_TYPES = [
|
|
2628
2746
|
"database",
|
|
@@ -2738,7 +2856,7 @@ function purposeFromBody(body) {
|
|
|
2738
2856
|
return text3 || null;
|
|
2739
2857
|
}
|
|
2740
2858
|
function toDetail(filePath, relPath, raw) {
|
|
2741
|
-
const parsed =
|
|
2859
|
+
const parsed = matter7(raw);
|
|
2742
2860
|
const fm = parsed.data ?? {};
|
|
2743
2861
|
const body = parsed.content.trim();
|
|
2744
2862
|
const id = str(fm.id) ?? "";
|
|
@@ -2800,7 +2918,7 @@ function parseWorkItemResources(fm) {
|
|
|
2800
2918
|
}
|
|
2801
2919
|
|
|
2802
2920
|
// ../cli/src/core/deprecation.ts
|
|
2803
|
-
import
|
|
2921
|
+
import matter8 from "gray-matter";
|
|
2804
2922
|
var DEPRECATION_ARTIFACT_TYPE = "deprecation-plan";
|
|
2805
2923
|
var DEPRECATION_PLANS_DIR = "knowledge/legacy/deprecations";
|
|
2806
2924
|
var TARGET_TYPES = [
|
|
@@ -2991,7 +3109,7 @@ function getDeprecationPlan(dir, id) {
|
|
|
2991
3109
|
);
|
|
2992
3110
|
if (!match) return null;
|
|
2993
3111
|
const raw = readFile(match.filePath);
|
|
2994
|
-
const parsed =
|
|
3112
|
+
const parsed = matter8(raw);
|
|
2995
3113
|
const fm = parsed.data ?? {};
|
|
2996
3114
|
const body = parsed.content.trim();
|
|
2997
3115
|
const targets = parseTargets(fm.targets);
|
|
@@ -3058,14 +3176,14 @@ var ALL_STATUSES = ["draft", "ready", "in-progress", "blocked", "completed"];
|
|
|
3058
3176
|
function scopeStatuses(scope) {
|
|
3059
3177
|
return scope === "all" ? { included: ALL_STATUSES, excluded: ["archived"] } : { included: ACTIVE_STATUSES, excluded: ["completed", "archived"] };
|
|
3060
3178
|
}
|
|
3061
|
-
function
|
|
3179
|
+
function toPosix5(p) {
|
|
3062
3180
|
return p.replace(/\\/g, "/");
|
|
3063
3181
|
}
|
|
3064
3182
|
function slug(s) {
|
|
3065
3183
|
return s.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
3066
3184
|
}
|
|
3067
3185
|
function isAdr(a2) {
|
|
3068
|
-
return
|
|
3186
|
+
return toPosix5(a2.filePath).includes("/tech/decisions/") && Boolean(a2.type);
|
|
3069
3187
|
}
|
|
3070
3188
|
function buildGraph(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
|
|
3071
3189
|
const scope = opts.scope ?? "active";
|
|
@@ -3176,6 +3294,27 @@ function buildGraph(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
|
|
|
3176
3294
|
}
|
|
3177
3295
|
}
|
|
3178
3296
|
}
|
|
3297
|
+
for (const plan of discoverDeliveryPlans(dir)) {
|
|
3298
|
+
const planId = `delivery-plan:${slug(plan.id) || plan.id}`;
|
|
3299
|
+
addNode({ id: planId, type: "delivery-plan", label: `${plan.id} ${plan.title}`.trim(), path: plan.relPath, status: plan.status });
|
|
3300
|
+
for (const m2 of plan.milestones) {
|
|
3301
|
+
const milestoneId = `milestone:${slug(plan.id)}:${slug(m2.id) || m2.id}`;
|
|
3302
|
+
addNode({ id: milestoneId, type: "milestone", label: `${m2.id} ${m2.title}`.trim(), path: plan.relPath });
|
|
3303
|
+
addEdge(milestoneId, planId, "belongs_to");
|
|
3304
|
+
}
|
|
3305
|
+
}
|
|
3306
|
+
for (const epic of discoverEpics(dir)) {
|
|
3307
|
+
const epicNodeId = `epic:${slug(epic.id) || epic.id}`;
|
|
3308
|
+
addNode({ id: epicNodeId, type: "epic", label: `${epic.id} ${epic.title}`.trim(), path: epic.relPath, status: epic.status });
|
|
3309
|
+
if (epic.plan) {
|
|
3310
|
+
addEdge(epicNodeId, `delivery-plan:${slug(epic.plan)}`, "belongs_to");
|
|
3311
|
+
if (epic.milestone) addEdge(epicNodeId, `milestone:${slug(epic.plan)}:${slug(epic.milestone)}`, "belongs_to");
|
|
3312
|
+
}
|
|
3313
|
+
for (const dep of epic.dependsOn) addEdge(epicNodeId, `epic:${slug(dep)}`, "depends_on");
|
|
3314
|
+
}
|
|
3315
|
+
for (const ini of discoverInitiatives(dir)) {
|
|
3316
|
+
if (ini.epic) addEdge(`initiative:${slug(ini.id) || ini.id}`, `epic:${slug(ini.epic)}`, "belongs_to");
|
|
3317
|
+
}
|
|
3179
3318
|
for (const wi of selectedWIs) {
|
|
3180
3319
|
const id = wi.id || wi.title;
|
|
3181
3320
|
if (!id || !id.trim()) continue;
|
|
@@ -3347,14 +3486,14 @@ function loadGraphSummary(dir) {
|
|
|
3347
3486
|
|
|
3348
3487
|
// ../cli/src/core/graph-hints.ts
|
|
3349
3488
|
var KNOWLEDGE4 = "knowledge";
|
|
3350
|
-
function
|
|
3489
|
+
function toPosix6(p) {
|
|
3351
3490
|
return p.replace(/\\/g, "/");
|
|
3352
3491
|
}
|
|
3353
3492
|
function slug2(s) {
|
|
3354
3493
|
return s.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
3355
3494
|
}
|
|
3356
3495
|
function isAdr2(a2) {
|
|
3357
|
-
return
|
|
3496
|
+
return toPosix6(a2.filePath).includes("/tech/decisions/") && Boolean(a2.type);
|
|
3358
3497
|
}
|
|
3359
3498
|
function capabilityHeadings(dir) {
|
|
3360
3499
|
const p = join(dir, KNOWLEDGE4, "product", "capabilities.md");
|
|
@@ -3589,7 +3728,7 @@ function loadGraphHints(dir) {
|
|
|
3589
3728
|
}
|
|
3590
3729
|
|
|
3591
3730
|
// ../cli/src/services/installed-skills.ts
|
|
3592
|
-
import
|
|
3731
|
+
import matter9 from "gray-matter";
|
|
3593
3732
|
var SKILLS_DIR = join("knowledge", "skills");
|
|
3594
3733
|
function discoverInstalledSkills(dir) {
|
|
3595
3734
|
const base = join(dir, SKILLS_DIR);
|
|
@@ -3599,7 +3738,7 @@ function discoverInstalledSkills(dir) {
|
|
|
3599
3738
|
const skillFile = join(base, entry, "skill.md");
|
|
3600
3739
|
if (!isFile(skillFile)) continue;
|
|
3601
3740
|
try {
|
|
3602
|
-
const { data } =
|
|
3741
|
+
const { data } = matter9(readFile(skillFile));
|
|
3603
3742
|
if (data.type && String(data.type) !== "skill") continue;
|
|
3604
3743
|
const id = String(data.id ?? entry);
|
|
3605
3744
|
out.push({
|
|
@@ -4705,7 +4844,7 @@ function readPocSummary(dir) {
|
|
|
4705
4844
|
|
|
4706
4845
|
// ../cli/src/core/poc-report.ts
|
|
4707
4846
|
import { createHash } from "crypto";
|
|
4708
|
-
import
|
|
4847
|
+
import matter10 from "gray-matter";
|
|
4709
4848
|
var POC_REPORT_PREFIX = "poc-report-v";
|
|
4710
4849
|
var SECRET_LINE = /(^|\s)(?:[A-Z][A-Z0-9_]*(?:TOKEN|SECRET|PASSWORD|API_KEY|PRIVATE_KEY)|(?:token|secret|password|api[_-]?key))\s*[:=]\s*\S+/i;
|
|
4711
4850
|
var reportName = /^poc-report-v(\d{3,})\.md$/;
|
|
@@ -4733,7 +4872,7 @@ function reportFiles(dir) {
|
|
|
4733
4872
|
const match = name.match(reportName);
|
|
4734
4873
|
if (!match) return [];
|
|
4735
4874
|
try {
|
|
4736
|
-
const parsed =
|
|
4875
|
+
const parsed = matter10(readFile(join(delivery, name)));
|
|
4737
4876
|
const fingerprint = typeof parsed.data.source_fingerprint === "string" ? parsed.data.source_fingerprint : "";
|
|
4738
4877
|
const manifest = parsed.data.source_manifest;
|
|
4739
4878
|
const sourceManifest = manifest && typeof manifest === "object" && !Array.isArray(manifest) ? Object.fromEntries(Object.entries(manifest).filter(([, digest]) => typeof digest === "string")) : {};
|
|
@@ -5379,7 +5518,7 @@ function deriveDeliveryCompletedStatus(dir) {
|
|
|
5379
5518
|
}
|
|
5380
5519
|
|
|
5381
5520
|
// ../cli/src/core/assets.ts
|
|
5382
|
-
import
|
|
5521
|
+
import matter11 from "gray-matter";
|
|
5383
5522
|
|
|
5384
5523
|
// ../cli/src/core/version.ts
|
|
5385
5524
|
import { fileURLToPath } from "url";
|
|
@@ -6343,9 +6482,9 @@ var RESPONSIBILITY_MATRIX = {
|
|
|
6343
6482
|
},
|
|
6344
6483
|
"roadmap-agent": {
|
|
6345
6484
|
agent: "roadmap-agent",
|
|
6346
|
-
responsibleFor: ["Roadmap", "Initiatives", "Work Item candidates"],
|
|
6485
|
+
responsibleFor: ["Roadmap", "Initiatives", "Work Item candidates", "Delivery Plans and Epics (optional planning layer)"],
|
|
6347
6486
|
produces: ["knowledge/delivery/roadmap.md"],
|
|
6348
|
-
canSuggest: ["kaddo create --from roadmap", "work-item-agent"],
|
|
6487
|
+
canSuggest: ["kaddo create --from roadmap", "work-item-agent", "kaddo plan create", "kaddo epic create"],
|
|
6349
6488
|
cannotSuggest: ["branches", "commits", "pull requests", "code"],
|
|
6350
6489
|
next: ["kaddo create --from roadmap", "work-item-agent"]
|
|
6351
6490
|
},
|
|
@@ -8846,7 +8985,7 @@ function canonicalSkills() {
|
|
|
8846
8985
|
}
|
|
8847
8986
|
function versionOf(content) {
|
|
8848
8987
|
try {
|
|
8849
|
-
const v =
|
|
8988
|
+
const v = matter11(content).data.version;
|
|
8850
8989
|
return v === void 0 || v === null ? null : String(v);
|
|
8851
8990
|
} catch {
|
|
8852
8991
|
return null;
|
|
@@ -9038,19 +9177,19 @@ function parseWorkItemSource(frontmatter) {
|
|
|
9038
9177
|
const rawSource = frontmatter.source;
|
|
9039
9178
|
if (rawSource != null && typeof rawSource === "object" && !Array.isArray(rawSource)) {
|
|
9040
9179
|
const obj = rawSource;
|
|
9041
|
-
const t =
|
|
9180
|
+
const t = optStr3(obj.type);
|
|
9042
9181
|
const type = t && isValidSource(t) ? t : "unknown";
|
|
9043
9182
|
return {
|
|
9044
9183
|
type,
|
|
9045
|
-
id:
|
|
9046
|
-
title:
|
|
9047
|
-
context:
|
|
9048
|
-
provider:
|
|
9049
|
-
integration:
|
|
9050
|
-
url:
|
|
9051
|
-
imported_at:
|
|
9052
|
-
external_updated_at:
|
|
9053
|
-
synced_at:
|
|
9184
|
+
id: optStr3(obj.id) ?? optStr3(frontmatter.source_id),
|
|
9185
|
+
title: optStr3(obj.title) ?? optStr3(frontmatter.source_title),
|
|
9186
|
+
context: optStr3(obj.context) ?? optStr3(frontmatter.source_context),
|
|
9187
|
+
provider: optStr3(obj.provider) ?? optStr3(frontmatter.source_provider),
|
|
9188
|
+
integration: optStr3(obj.integration) ?? optStr3(frontmatter.source_integration),
|
|
9189
|
+
url: optStr3(obj.url) ?? optStr3(frontmatter.source_url),
|
|
9190
|
+
imported_at: optStr3(obj.imported_at) ?? optStr3(frontmatter.source_imported_at),
|
|
9191
|
+
external_updated_at: optStr3(obj.external_updated_at) ?? optStr3(frontmatter.source_external_updated_at),
|
|
9192
|
+
synced_at: optStr3(obj.synced_at) ?? optStr3(frontmatter.source_synced_at),
|
|
9054
9193
|
inferred: obj.inferred === true || obj.inferred === "true",
|
|
9055
9194
|
reason: t && !isValidSource(t) ? `Invalid source type in object: "${t}".` : void 0
|
|
9056
9195
|
};
|
|
@@ -9059,28 +9198,28 @@ function parseWorkItemSource(frontmatter) {
|
|
|
9059
9198
|
if (raw && isValidSource(raw)) {
|
|
9060
9199
|
return {
|
|
9061
9200
|
type: raw,
|
|
9062
|
-
id:
|
|
9063
|
-
title:
|
|
9064
|
-
context:
|
|
9065
|
-
provider:
|
|
9066
|
-
url:
|
|
9067
|
-
imported_at:
|
|
9068
|
-
external_updated_at:
|
|
9069
|
-
synced_at:
|
|
9201
|
+
id: optStr3(frontmatter.source_id),
|
|
9202
|
+
title: optStr3(frontmatter.source_title),
|
|
9203
|
+
context: optStr3(frontmatter.source_context),
|
|
9204
|
+
provider: optStr3(frontmatter.source_provider),
|
|
9205
|
+
url: optStr3(frontmatter.source_url),
|
|
9206
|
+
imported_at: optStr3(frontmatter.source_imported_at),
|
|
9207
|
+
external_updated_at: optStr3(frontmatter.source_external_updated_at),
|
|
9208
|
+
synced_at: optStr3(frontmatter.source_synced_at),
|
|
9070
9209
|
inferred: false
|
|
9071
9210
|
};
|
|
9072
9211
|
}
|
|
9073
9212
|
if (raw && !isValidSource(raw)) {
|
|
9074
9213
|
return {
|
|
9075
9214
|
type: "unknown",
|
|
9076
|
-
id:
|
|
9077
|
-
title:
|
|
9078
|
-
context:
|
|
9079
|
-
provider:
|
|
9080
|
-
url:
|
|
9081
|
-
imported_at:
|
|
9082
|
-
external_updated_at:
|
|
9083
|
-
synced_at:
|
|
9215
|
+
id: optStr3(frontmatter.source_id),
|
|
9216
|
+
title: optStr3(frontmatter.source_title),
|
|
9217
|
+
context: optStr3(frontmatter.source_context),
|
|
9218
|
+
provider: optStr3(frontmatter.source_provider),
|
|
9219
|
+
url: optStr3(frontmatter.source_url),
|
|
9220
|
+
imported_at: optStr3(frontmatter.source_imported_at),
|
|
9221
|
+
external_updated_at: optStr3(frontmatter.source_external_updated_at),
|
|
9222
|
+
synced_at: optStr3(frontmatter.source_synced_at),
|
|
9084
9223
|
inferred: true,
|
|
9085
9224
|
reason: `Invalid source value: "${raw}".`
|
|
9086
9225
|
};
|
|
@@ -9088,8 +9227,8 @@ function parseWorkItemSource(frontmatter) {
|
|
|
9088
9227
|
if (frontmatter.source_work_item_candidate || frontmatter.source_roadmap_initiative) {
|
|
9089
9228
|
return {
|
|
9090
9229
|
type: "roadmap",
|
|
9091
|
-
id:
|
|
9092
|
-
title:
|
|
9230
|
+
id: optStr3(frontmatter.source_id) ?? optStr3(frontmatter.source_work_item_candidate),
|
|
9231
|
+
title: optStr3(frontmatter.source_initiative_title),
|
|
9093
9232
|
inferred: true,
|
|
9094
9233
|
reason: "Inferred from legacy roadmap fields."
|
|
9095
9234
|
};
|
|
@@ -9097,7 +9236,7 @@ function parseWorkItemSource(frontmatter) {
|
|
|
9097
9236
|
if (frontmatter.source_id) {
|
|
9098
9237
|
return {
|
|
9099
9238
|
type: "unknown",
|
|
9100
|
-
id:
|
|
9239
|
+
id: optStr3(frontmatter.source_id),
|
|
9101
9240
|
inferred: true,
|
|
9102
9241
|
reason: "Has source_id but no source type."
|
|
9103
9242
|
};
|
|
@@ -9113,7 +9252,7 @@ function renderSourceCompact(source3) {
|
|
|
9113
9252
|
if (source3.id) parts.push(source3.id);
|
|
9114
9253
|
return parts.join(" \xB7 ");
|
|
9115
9254
|
}
|
|
9116
|
-
function
|
|
9255
|
+
function optStr3(v) {
|
|
9117
9256
|
if (typeof v === "string" && v.trim()) return v.trim();
|
|
9118
9257
|
return void 0;
|
|
9119
9258
|
}
|
|
@@ -9686,7 +9825,7 @@ function renderRouteCompact(route) {
|
|
|
9686
9825
|
}
|
|
9687
9826
|
|
|
9688
9827
|
// ../cli/src/core/metadata-health.ts
|
|
9689
|
-
import
|
|
9828
|
+
import matter12 from "gray-matter";
|
|
9690
9829
|
var KNOWLEDGE_FILES2 = [
|
|
9691
9830
|
"knowledge/business/business.md",
|
|
9692
9831
|
"knowledge/product/product.md",
|
|
@@ -9706,7 +9845,7 @@ function analyzeMetadataHealth(dir) {
|
|
|
9706
9845
|
let data;
|
|
9707
9846
|
try {
|
|
9708
9847
|
const raw = readFile(p);
|
|
9709
|
-
data =
|
|
9848
|
+
data = matter12(raw).data;
|
|
9710
9849
|
} catch {
|
|
9711
9850
|
continue;
|
|
9712
9851
|
}
|
|
@@ -9738,7 +9877,7 @@ function analyzeMetadataHealth(dir) {
|
|
|
9738
9877
|
if (isFile(p) && entry.endsWith(".md")) {
|
|
9739
9878
|
try {
|
|
9740
9879
|
const raw = readFile(p);
|
|
9741
|
-
const fm =
|
|
9880
|
+
const fm = matter12(raw).data;
|
|
9742
9881
|
if (fm.status === "done") {
|
|
9743
9882
|
const rel = p.replace(dir + "/", "").replace(dir + "\\", "").replace(/\\/g, "/");
|
|
9744
9883
|
findings.push({
|
|
@@ -10505,7 +10644,7 @@ function renderExplanationAgent(exp) {
|
|
|
10505
10644
|
var HISTORY_DIR = ".kaddo/history";
|
|
10506
10645
|
var RUNS_PATH = `${HISTORY_DIR}/guard-runs.jsonl`;
|
|
10507
10646
|
var SUMMARY_PATH = `${HISTORY_DIR}/guard-summary.json`;
|
|
10508
|
-
function
|
|
10647
|
+
function toPosix7(p) {
|
|
10509
10648
|
return p.replace(/\\/g, "/");
|
|
10510
10649
|
}
|
|
10511
10650
|
function loadGuardRuns(dir) {
|
|
@@ -10542,7 +10681,7 @@ function resolveDriftThreads(runs) {
|
|
|
10542
10681
|
let resolvedAt;
|
|
10543
10682
|
for (const r2 of sorted) {
|
|
10544
10683
|
if (r2.generated_at <= lastWarn) continue;
|
|
10545
|
-
const touched = r2.touched_files.some((f3) =>
|
|
10684
|
+
const touched = r2.touched_files.some((f3) => toPosix7(f3) === codePath);
|
|
10546
10685
|
const warnedHere = r2.warnings.some((x2) => x2.code_path === codePath);
|
|
10547
10686
|
const updatedRelated = r2.updated_artifacts.some((a2) => related.has(a2));
|
|
10548
10687
|
if (touched && updatedRelated && !warnedHere) {
|
|
@@ -10759,7 +10898,7 @@ function buildImpactReport(dir, opts = {}, now = /* @__PURE__ */ new Date()) {
|
|
|
10759
10898
|
}
|
|
10760
10899
|
}
|
|
10761
10900
|
try {
|
|
10762
|
-
const body =
|
|
10901
|
+
const body = matter13(readFile(wi.filePath)).content;
|
|
10763
10902
|
if (hasSection(body, /acceptance|criterios de aceptaci/i)) withAcceptance++;
|
|
10764
10903
|
else gaps.missing_acceptance_criteria.push(gapItem(wi, "Add an `## Acceptance Criteria` section."));
|
|
10765
10904
|
if (hasSection(body, /definition of done|^#{1,6}\s*dod\b|definici[oó]n de (terminado|hecho)/i)) withDoD++;
|
|
@@ -11752,25 +11891,25 @@ function serializeDriftJson(r2) {
|
|
|
11752
11891
|
}
|
|
11753
11892
|
|
|
11754
11893
|
// src/workitems.ts
|
|
11755
|
-
import
|
|
11894
|
+
import matter14 from "gray-matter";
|
|
11756
11895
|
var VALID_SOURCES2 = /* @__PURE__ */ new Set(["manual", "roadmap", "jira", "github", "notion", "xlsx", "csv", "api", "external", "unknown"]);
|
|
11757
|
-
function
|
|
11896
|
+
function optStr4(v) {
|
|
11758
11897
|
return typeof v === "string" && v.trim() ? v.trim() : void 0;
|
|
11759
11898
|
}
|
|
11760
11899
|
function parseSource(data) {
|
|
11761
11900
|
const rawSource = data.source;
|
|
11762
11901
|
if (rawSource != null && typeof rawSource === "object" && !Array.isArray(rawSource)) {
|
|
11763
11902
|
const obj = rawSource;
|
|
11764
|
-
const t =
|
|
11903
|
+
const t = optStr4(obj.type);
|
|
11765
11904
|
const type = t && VALID_SOURCES2.has(t) ? t : "unknown";
|
|
11766
|
-
return { type, id:
|
|
11905
|
+
return { type, id: optStr4(obj.id) ?? optStr4(data.source_id), title: optStr4(obj.title) ?? optStr4(data.source_title), url: optStr4(obj.url) ?? optStr4(data.source_url), provider: optStr4(obj.provider) ?? optStr4(data.source_provider), inferred: obj.inferred === true || obj.inferred === "true" };
|
|
11767
11906
|
}
|
|
11768
11907
|
const raw = rawSource ? String(rawSource) : "";
|
|
11769
11908
|
if (raw && VALID_SOURCES2.has(raw)) {
|
|
11770
|
-
return { type: raw, id:
|
|
11909
|
+
return { type: raw, id: optStr4(data.source_id), title: optStr4(data.source_title), url: optStr4(data.source_url), provider: optStr4(data.source_provider), inferred: false };
|
|
11771
11910
|
}
|
|
11772
11911
|
if (data.source_work_item_candidate || data.source_roadmap_initiative) {
|
|
11773
|
-
return { type: "roadmap", id:
|
|
11912
|
+
return { type: "roadmap", id: optStr4(data.source_id) ?? optStr4(data.source_work_item_candidate), title: optStr4(data.source_initiative_title), inferred: true };
|
|
11774
11913
|
}
|
|
11775
11914
|
return { type: "unknown", inferred: true };
|
|
11776
11915
|
}
|
|
@@ -11843,7 +11982,7 @@ function listWorkItems(root) {
|
|
|
11843
11982
|
if (raw === null) continue;
|
|
11844
11983
|
let parsed;
|
|
11845
11984
|
try {
|
|
11846
|
-
parsed =
|
|
11985
|
+
parsed = matter14(raw);
|
|
11847
11986
|
} catch {
|
|
11848
11987
|
continue;
|
|
11849
11988
|
}
|
|
@@ -12363,14 +12502,14 @@ function getPrompt(root, name) {
|
|
|
12363
12502
|
}
|
|
12364
12503
|
|
|
12365
12504
|
// src/tools.ts
|
|
12366
|
-
import
|
|
12505
|
+
import matter26 from "gray-matter";
|
|
12367
12506
|
|
|
12368
12507
|
// ../cli/dist/core.js
|
|
12369
12508
|
import fs3 from "fs";
|
|
12370
12509
|
import path3 from "path";
|
|
12371
12510
|
import { z as z3 } from "zod";
|
|
12372
12511
|
import { parse as parseYaml10, stringify as stringifyYaml5 } from "yaml";
|
|
12373
|
-
import
|
|
12512
|
+
import matter15 from "gray-matter";
|
|
12374
12513
|
import matter22 from "gray-matter";
|
|
12375
12514
|
import { parse as parseYaml62, stringify as stringifyYaml42 } from "yaml";
|
|
12376
12515
|
import { parse as parseYaml22 } from "yaml";
|
|
@@ -12381,35 +12520,37 @@ import matter32 from "gray-matter";
|
|
|
12381
12520
|
import matter42 from "gray-matter";
|
|
12382
12521
|
import matter52 from "gray-matter";
|
|
12383
12522
|
import matter62 from "gray-matter";
|
|
12523
|
+
import matter72 from "gray-matter";
|
|
12384
12524
|
import { parse as parseYaml72 } from "yaml";
|
|
12385
12525
|
import { createHash as createHash2 } from "crypto";
|
|
12386
|
-
import matter72 from "gray-matter";
|
|
12387
12526
|
import matter82 from "gray-matter";
|
|
12527
|
+
import matter92 from "gray-matter";
|
|
12388
12528
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
12389
12529
|
import { dirname as dirname2, join as join22, parse as parse2 } from "path";
|
|
12390
12530
|
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
12391
|
-
import matter92 from "gray-matter";
|
|
12392
12531
|
import matter102 from "gray-matter";
|
|
12393
|
-
import { parse as parseYaml82 } from "yaml";
|
|
12394
12532
|
import matter112 from "gray-matter";
|
|
12395
|
-
import
|
|
12533
|
+
import { parse as parseYaml82 } from "yaml";
|
|
12396
12534
|
import matter132 from "gray-matter";
|
|
12535
|
+
import matter122 from "gray-matter";
|
|
12536
|
+
import matter142 from "gray-matter";
|
|
12537
|
+
import matter152 from "gray-matter";
|
|
12538
|
+
import matter16 from "gray-matter";
|
|
12397
12539
|
import { parse as parseYaml92, stringify as stringifyYaml52 } from "yaml";
|
|
12398
12540
|
import fs4 from "fs";
|
|
12399
12541
|
import path4 from "path";
|
|
12400
12542
|
import crypto2 from "crypto";
|
|
12401
|
-
import
|
|
12543
|
+
import matter17 from "gray-matter";
|
|
12402
12544
|
import crypto3 from "crypto";
|
|
12403
12545
|
import fs5 from "fs";
|
|
12404
|
-
import matter15 from "gray-matter";
|
|
12405
|
-
import matter16 from "gray-matter";
|
|
12406
|
-
import { parse as parseYaml102, stringify as stringifyYaml6 } from "yaml";
|
|
12407
|
-
import { parse as parseYaml11, stringify as stringifyYaml7 } from "yaml";
|
|
12408
|
-
import matter17 from "gray-matter";
|
|
12409
12546
|
import matter18 from "gray-matter";
|
|
12410
12547
|
import matter19 from "gray-matter";
|
|
12548
|
+
import { parse as parseYaml102, stringify as stringifyYaml6 } from "yaml";
|
|
12549
|
+
import { parse as parseYaml11, stringify as stringifyYaml7 } from "yaml";
|
|
12411
12550
|
import matter20 from "gray-matter";
|
|
12412
12551
|
import matter21 from "gray-matter";
|
|
12552
|
+
import matter222 from "gray-matter";
|
|
12553
|
+
import matter23 from "gray-matter";
|
|
12413
12554
|
function exists2(p) {
|
|
12414
12555
|
return fs3.existsSync(p);
|
|
12415
12556
|
}
|
|
@@ -12585,7 +12726,7 @@ function parseImpactAnalysis2(data) {
|
|
|
12585
12726
|
}
|
|
12586
12727
|
function parseArtifact2(filePath, raw) {
|
|
12587
12728
|
try {
|
|
12588
|
-
const { data } =
|
|
12729
|
+
const { data } = matter15(raw);
|
|
12589
12730
|
const globs = Array.isArray(data.code) ? data.code.filter(Boolean) : [];
|
|
12590
12731
|
return {
|
|
12591
12732
|
filePath,
|
|
@@ -12678,16 +12819,16 @@ function emptyLifecycleCounts2() {
|
|
|
12678
12819
|
return Object.fromEntries(LIFECYCLE_STATES3.map((s) => [s, 0]));
|
|
12679
12820
|
}
|
|
12680
12821
|
var ARCH_DIR3 = "knowledge";
|
|
12681
|
-
function
|
|
12822
|
+
function toPosix8(p) {
|
|
12682
12823
|
return p.replace(/\\/g, "/");
|
|
12683
12824
|
}
|
|
12684
12825
|
function relativeTo2(dir, filePath) {
|
|
12685
|
-
const base =
|
|
12686
|
-
const full =
|
|
12826
|
+
const base = toPosix8(dir).replace(/\/+$/, "");
|
|
12827
|
+
const full = toPosix8(filePath);
|
|
12687
12828
|
return full.startsWith(base + "/") ? full.slice(base.length + 1) : full;
|
|
12688
12829
|
}
|
|
12689
12830
|
function isWorkItemArtifact2(a2) {
|
|
12690
|
-
return
|
|
12831
|
+
return toPosix8(a2.filePath).includes("/delivery/work-items/") && Boolean(a2.type);
|
|
12691
12832
|
}
|
|
12692
12833
|
function classifyLayer2(posixPath) {
|
|
12693
12834
|
if (posixPath.includes("/knowledge/business/")) return "business";
|
|
@@ -12699,7 +12840,7 @@ function classifyLayer2(posixPath) {
|
|
|
12699
12840
|
return "unknown";
|
|
12700
12841
|
}
|
|
12701
12842
|
function enrich2(dir, a2) {
|
|
12702
|
-
const posix22 =
|
|
12843
|
+
const posix22 = toPosix8(a2.filePath);
|
|
12703
12844
|
const isWi = isWorkItemArtifact2(a2);
|
|
12704
12845
|
return {
|
|
12705
12846
|
...a2,
|
|
@@ -13960,12 +14101,12 @@ function isValidInitiativeTransition(from, to) {
|
|
|
13960
14101
|
function toPosix22(p) {
|
|
13961
14102
|
return p.replace(/\\/g, "/");
|
|
13962
14103
|
}
|
|
13963
|
-
function
|
|
14104
|
+
function optStr5(v) {
|
|
13964
14105
|
if (typeof v === "string" && v.trim()) return v.trim();
|
|
13965
14106
|
if (typeof v === "number") return String(v);
|
|
13966
14107
|
return void 0;
|
|
13967
14108
|
}
|
|
13968
|
-
function
|
|
14109
|
+
function strList3(v) {
|
|
13969
14110
|
if (!Array.isArray(v)) return [];
|
|
13970
14111
|
return v.map((x2) => typeof x2 === "string" ? x2.trim() : String(x2)).filter(Boolean);
|
|
13971
14112
|
}
|
|
@@ -13976,19 +14117,19 @@ function parseCandidates2(data) {
|
|
|
13976
14117
|
for (const entry of raw) {
|
|
13977
14118
|
if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
|
|
13978
14119
|
const o2 = entry;
|
|
13979
|
-
const id =
|
|
13980
|
-
const title =
|
|
14120
|
+
const id = optStr5(o2.id);
|
|
14121
|
+
const title = optStr5(o2.title);
|
|
13981
14122
|
if (!id || !title) continue;
|
|
13982
14123
|
out.push({
|
|
13983
14124
|
id,
|
|
13984
14125
|
title,
|
|
13985
|
-
type:
|
|
13986
|
-
suggestedKnowledgeLevel:
|
|
13987
|
-
expectedValue:
|
|
13988
|
-
notes:
|
|
13989
|
-
sourceSignals:
|
|
13990
|
-
dependencies:
|
|
13991
|
-
materializedAs:
|
|
14126
|
+
type: optStr5(o2.type),
|
|
14127
|
+
suggestedKnowledgeLevel: optStr5(o2.suggested_knowledge_level ?? o2.knowledge_level),
|
|
14128
|
+
expectedValue: optStr5(o2.expected_value),
|
|
14129
|
+
notes: optStr5(o2.notes),
|
|
14130
|
+
sourceSignals: strList3(o2.source_signals),
|
|
14131
|
+
dependencies: strList3(o2.dependencies),
|
|
14132
|
+
materializedAs: optStr5(o2.materialized_as) ?? null
|
|
13992
14133
|
});
|
|
13993
14134
|
}
|
|
13994
14135
|
return out;
|
|
@@ -14000,15 +14141,15 @@ function parseExternalLinks2(data) {
|
|
|
14000
14141
|
for (const entry of raw) {
|
|
14001
14142
|
if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
|
|
14002
14143
|
const o2 = entry;
|
|
14003
|
-
const integration =
|
|
14004
|
-
const externalId =
|
|
14144
|
+
const integration = optStr5(o2.integration);
|
|
14145
|
+
const externalId = optStr5(o2.external_id);
|
|
14005
14146
|
if (!integration || !externalId) continue;
|
|
14006
14147
|
out.push({
|
|
14007
14148
|
integration,
|
|
14008
14149
|
externalId,
|
|
14009
|
-
externalType:
|
|
14010
|
-
url:
|
|
14011
|
-
externalStatus:
|
|
14150
|
+
externalType: optStr5(o2.external_type),
|
|
14151
|
+
url: optStr5(o2.url),
|
|
14152
|
+
externalStatus: optStr5(o2.external_status)
|
|
14012
14153
|
});
|
|
14013
14154
|
}
|
|
14014
14155
|
return out;
|
|
@@ -14021,23 +14162,26 @@ function parseInitiative2(dir, filePath, raw) {
|
|
|
14021
14162
|
return null;
|
|
14022
14163
|
}
|
|
14023
14164
|
const data = parsed.data;
|
|
14024
|
-
if (
|
|
14025
|
-
const id =
|
|
14165
|
+
if (optStr5(data.type) !== "initiative") return null;
|
|
14166
|
+
const id = optStr5(data.id);
|
|
14026
14167
|
if (!id) return null;
|
|
14027
|
-
const statusRaw =
|
|
14168
|
+
const statusRaw = optStr5(data.status) ?? "planned";
|
|
14028
14169
|
const status = isValidInitiativeStatus2(statusRaw) ? statusRaw : "planned";
|
|
14029
14170
|
return {
|
|
14030
14171
|
id,
|
|
14031
|
-
title:
|
|
14172
|
+
title: optStr5(data.title) ?? id,
|
|
14032
14173
|
status,
|
|
14033
|
-
horizon:
|
|
14034
|
-
priority:
|
|
14035
|
-
knowledgeLevel:
|
|
14036
|
-
domains:
|
|
14037
|
-
relatedCapabilities:
|
|
14038
|
-
source:
|
|
14039
|
-
sourceId:
|
|
14040
|
-
createdAt:
|
|
14174
|
+
horizon: optStr5(data.horizon) ?? null,
|
|
14175
|
+
priority: optStr5(data.priority) ?? null,
|
|
14176
|
+
knowledgeLevel: optStr5(data.knowledge_level) ?? null,
|
|
14177
|
+
domains: strList3(data.domains),
|
|
14178
|
+
relatedCapabilities: strList3(data.related_capabilities),
|
|
14179
|
+
source: optStr5(data.source) ?? null,
|
|
14180
|
+
sourceId: optStr5(data.source_id) ?? null,
|
|
14181
|
+
createdAt: optStr5(data.created_at) ?? null,
|
|
14182
|
+
epic: optStr5(data.epic) ?? null,
|
|
14183
|
+
owners: strList3(data.owners),
|
|
14184
|
+
contributors: strList3(data.contributors),
|
|
14041
14185
|
externalLinks: parseExternalLinks2(data),
|
|
14042
14186
|
candidates: parseCandidates2(data),
|
|
14043
14187
|
filePath,
|
|
@@ -14139,6 +14283,290 @@ function suggestInitiativesForExternalItem(dir, integration, externalId) {
|
|
|
14139
14283
|
)
|
|
14140
14284
|
);
|
|
14141
14285
|
}
|
|
14286
|
+
var DELIVERY_PLANS_DIR2 = "knowledge/delivery/plans";
|
|
14287
|
+
var EPICS_DIR2 = "knowledge/delivery/epics";
|
|
14288
|
+
var PLAN_STATES2 = ["draft", "in-progress", "completed", "cancelled"];
|
|
14289
|
+
var EPIC_STATES2 = ["planned", "in-progress", "completed", "cancelled"];
|
|
14290
|
+
function toPosix32(p) {
|
|
14291
|
+
return p.replace(/\\/g, "/");
|
|
14292
|
+
}
|
|
14293
|
+
function optStr22(v) {
|
|
14294
|
+
if (typeof v === "string" && v.trim()) return v.trim();
|
|
14295
|
+
if (typeof v === "number") return String(v);
|
|
14296
|
+
return void 0;
|
|
14297
|
+
}
|
|
14298
|
+
function strList22(v) {
|
|
14299
|
+
if (!Array.isArray(v)) return [];
|
|
14300
|
+
return v.map((x2) => typeof x2 === "string" ? x2.trim() : String(x2)).filter(Boolean);
|
|
14301
|
+
}
|
|
14302
|
+
function relFrom2(dir, filePath) {
|
|
14303
|
+
const d3 = toPosix32(dir);
|
|
14304
|
+
const f3 = toPosix32(filePath);
|
|
14305
|
+
return f3.startsWith(d3 + "/") ? f3.slice(d3.length + 1) : f3;
|
|
14306
|
+
}
|
|
14307
|
+
function parseMilestones2(data) {
|
|
14308
|
+
const raw = data.milestones;
|
|
14309
|
+
if (!Array.isArray(raw)) return [];
|
|
14310
|
+
const out = [];
|
|
14311
|
+
for (const entry of raw) {
|
|
14312
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
|
|
14313
|
+
const o2 = entry;
|
|
14314
|
+
const id = optStr22(o2.id);
|
|
14315
|
+
if (!id) continue;
|
|
14316
|
+
out.push({ id, title: optStr22(o2.title) ?? id, exitCriteria: strList22(o2.exit_criteria) });
|
|
14317
|
+
}
|
|
14318
|
+
return out;
|
|
14319
|
+
}
|
|
14320
|
+
function parseDeliveryPlan2(dir, filePath, raw) {
|
|
14321
|
+
let parsed;
|
|
14322
|
+
try {
|
|
14323
|
+
parsed = matter42(raw, {});
|
|
14324
|
+
} catch {
|
|
14325
|
+
return null;
|
|
14326
|
+
}
|
|
14327
|
+
const data = parsed.data;
|
|
14328
|
+
if (optStr22(data.type) !== "delivery-plan") return null;
|
|
14329
|
+
const id = optStr22(data.id);
|
|
14330
|
+
if (!id) return null;
|
|
14331
|
+
const statusRaw = optStr22(data.status) ?? "draft";
|
|
14332
|
+
const status = PLAN_STATES2.includes(statusRaw) ? statusRaw : "draft";
|
|
14333
|
+
return {
|
|
14334
|
+
id,
|
|
14335
|
+
title: optStr22(data.title) ?? id,
|
|
14336
|
+
status,
|
|
14337
|
+
objective: optStr22(data.objective) ?? null,
|
|
14338
|
+
milestones: parseMilestones2(data),
|
|
14339
|
+
dependsOn: strList22(data.depends_on),
|
|
14340
|
+
successCriteria: strList22(data.success_criteria),
|
|
14341
|
+
risks: strList22(data.risks),
|
|
14342
|
+
references: strList22(data.references),
|
|
14343
|
+
filePath,
|
|
14344
|
+
relPath: relFrom2(dir, filePath),
|
|
14345
|
+
body: parsed.content.trim(),
|
|
14346
|
+
rawFrontmatter: data
|
|
14347
|
+
};
|
|
14348
|
+
}
|
|
14349
|
+
function parseEpic2(dir, filePath, raw) {
|
|
14350
|
+
let parsed;
|
|
14351
|
+
try {
|
|
14352
|
+
parsed = matter42(raw, {});
|
|
14353
|
+
} catch {
|
|
14354
|
+
return null;
|
|
14355
|
+
}
|
|
14356
|
+
const data = parsed.data;
|
|
14357
|
+
if (optStr22(data.type) !== "epic") return null;
|
|
14358
|
+
const id = optStr22(data.id);
|
|
14359
|
+
if (!id) return null;
|
|
14360
|
+
const statusRaw = optStr22(data.status) ?? "planned";
|
|
14361
|
+
const status = EPIC_STATES2.includes(statusRaw) ? statusRaw : "planned";
|
|
14362
|
+
return {
|
|
14363
|
+
id,
|
|
14364
|
+
title: optStr22(data.title) ?? id,
|
|
14365
|
+
status,
|
|
14366
|
+
outcome: optStr22(data.outcome ?? data.expected_outcome) ?? null,
|
|
14367
|
+
plan: optStr22(data.plan) ?? null,
|
|
14368
|
+
milestone: optStr22(data.milestone) ?? null,
|
|
14369
|
+
dependsOn: strList22(data.depends_on),
|
|
14370
|
+
successCriteria: strList22(data.success_criteria),
|
|
14371
|
+
relatedCapabilities: strList22(data.related_capabilities),
|
|
14372
|
+
decisions: strList22(data.decisions),
|
|
14373
|
+
filePath,
|
|
14374
|
+
relPath: relFrom2(dir, filePath),
|
|
14375
|
+
body: parsed.content.trim(),
|
|
14376
|
+
rawFrontmatter: data
|
|
14377
|
+
};
|
|
14378
|
+
}
|
|
14379
|
+
function discoverIn2(dir, rel, parse22) {
|
|
14380
|
+
const d3 = join3(dir, rel);
|
|
14381
|
+
if (!exists2(d3)) return [];
|
|
14382
|
+
const out = [];
|
|
14383
|
+
for (const entry of readDir2(d3)) {
|
|
14384
|
+
if (!entry.endsWith(".md")) continue;
|
|
14385
|
+
const full = join3(d3, entry);
|
|
14386
|
+
if (!isFile2(full)) continue;
|
|
14387
|
+
const parsed = parse22(dir, full, readFile2(full));
|
|
14388
|
+
if (parsed) out.push(parsed);
|
|
14389
|
+
}
|
|
14390
|
+
return out;
|
|
14391
|
+
}
|
|
14392
|
+
function discoverDeliveryPlans2(dir) {
|
|
14393
|
+
return discoverIn2(dir, DELIVERY_PLANS_DIR2, parseDeliveryPlan2).sort((a2, b2) => a2.id.localeCompare(b2.id));
|
|
14394
|
+
}
|
|
14395
|
+
function discoverEpics2(dir) {
|
|
14396
|
+
return discoverIn2(dir, EPICS_DIR2, parseEpic2).sort((a2, b2) => a2.id.localeCompare(b2.id));
|
|
14397
|
+
}
|
|
14398
|
+
function getPlan(dir, id) {
|
|
14399
|
+
const want = id.trim().toLowerCase();
|
|
14400
|
+
return discoverDeliveryPlans2(dir).find((p) => p.id.toLowerCase() === want) ?? null;
|
|
14401
|
+
}
|
|
14402
|
+
function getEpic(dir, id) {
|
|
14403
|
+
const want = id.trim().toLowerCase();
|
|
14404
|
+
return discoverEpics2(dir).find((e) => e.id.toLowerCase() === want) ?? null;
|
|
14405
|
+
}
|
|
14406
|
+
function nextId(ids, prefix) {
|
|
14407
|
+
let max = 0;
|
|
14408
|
+
for (const id of ids) {
|
|
14409
|
+
const m2 = id.match(new RegExp(`^${prefix}-(\\d+)$`, "i"));
|
|
14410
|
+
if (m2) max = Math.max(max, Number.parseInt(m2[1], 10));
|
|
14411
|
+
}
|
|
14412
|
+
return `${prefix}-${String(max + 1).padStart(3, "0")}`;
|
|
14413
|
+
}
|
|
14414
|
+
function nextPlanId(dir) {
|
|
14415
|
+
return nextId(discoverDeliveryPlans2(dir).map((p) => p.id), "PLAN");
|
|
14416
|
+
}
|
|
14417
|
+
function nextEpicId(dir) {
|
|
14418
|
+
return nextId(discoverEpics2(dir).map((e) => e.id), "EP");
|
|
14419
|
+
}
|
|
14420
|
+
function epicsInPlan(dir, planId) {
|
|
14421
|
+
return discoverEpics2(dir).filter((e) => e.plan?.toLowerCase() === planId.toLowerCase());
|
|
14422
|
+
}
|
|
14423
|
+
function initiativesInEpic(dir, epicId) {
|
|
14424
|
+
return discoverInitiatives2(dir).filter((i) => i.epic?.toLowerCase() === epicId.toLowerCase());
|
|
14425
|
+
}
|
|
14426
|
+
function wiEpic(wi) {
|
|
14427
|
+
return optStr22(wi.rawFrontmatter?.epic);
|
|
14428
|
+
}
|
|
14429
|
+
function workItemsInEpic(dir, epicId) {
|
|
14430
|
+
const iniIds = new Set(initiativesInEpic(dir, epicId).map((i) => i.id.toLowerCase()));
|
|
14431
|
+
return discoverWorkItems2(dir).filter(
|
|
14432
|
+
(wi) => wiEpic(wi)?.toLowerCase() === epicId.toLowerCase() || wi.initiative && iniIds.has(wi.initiative.toLowerCase())
|
|
14433
|
+
);
|
|
14434
|
+
}
|
|
14435
|
+
function readinessOf(successCriteria, blockingDependencies, delivery) {
|
|
14436
|
+
const committedWorkComplete = delivery.total > 0 && delivery.completed === delivery.total;
|
|
14437
|
+
return {
|
|
14438
|
+
successCriteria,
|
|
14439
|
+
blockingDependencies,
|
|
14440
|
+
committedWorkComplete,
|
|
14441
|
+
// Needs real deliverables, declared criteria and no blocking dependency — never positive otherwise.
|
|
14442
|
+
meetsCompletionPreconditions: committedWorkComplete && successCriteria > 0 && blockingDependencies.length === 0,
|
|
14443
|
+
humanApprovalRequired: true
|
|
14444
|
+
// completion is never automatic
|
|
14445
|
+
};
|
|
14446
|
+
}
|
|
14447
|
+
function deliveryOf(wis, dir) {
|
|
14448
|
+
const byState = emptyLifecycleCounts2();
|
|
14449
|
+
const completedIds = new Set(
|
|
14450
|
+
discoverWorkItems2(dir).filter((w3) => w3.lifecycle === "completed").map((w3) => w3.id.toLowerCase())
|
|
14451
|
+
);
|
|
14452
|
+
let blocked = 0;
|
|
14453
|
+
for (const wi of wis) {
|
|
14454
|
+
const state = wi.lifecycle ?? "ready";
|
|
14455
|
+
byState[state]++;
|
|
14456
|
+
const deps = strList22(wi.rawFrontmatter?.depends_on);
|
|
14457
|
+
if (state !== "completed" && deps.some((d3) => !completedIds.has(d3.toLowerCase()))) blocked++;
|
|
14458
|
+
}
|
|
14459
|
+
return { total: wis.length, byState, blocked, completed: byState.completed };
|
|
14460
|
+
}
|
|
14461
|
+
function computeEpicProgress(dir, epic) {
|
|
14462
|
+
const inis = initiativesInEpic(dir, epic.id);
|
|
14463
|
+
const candidates = inis.reduce((n, i) => n + i.candidates.length, 0);
|
|
14464
|
+
const materialized = inis.reduce((n, i) => n + i.candidates.filter((c) => c.materializedAs).length, 0);
|
|
14465
|
+
const wis = workItemsInEpic(dir, epic.id);
|
|
14466
|
+
const delivery = deliveryOf(wis, dir);
|
|
14467
|
+
return {
|
|
14468
|
+
epic: epic.id,
|
|
14469
|
+
status: epic.status,
|
|
14470
|
+
planning: { initiatives: inis.length, candidates, materialized, remaining: Math.max(0, candidates - materialized) },
|
|
14471
|
+
delivery,
|
|
14472
|
+
readiness: readinessOf(epic.successCriteria.length, unmetDependencies(dir, epic.dependsOn), delivery)
|
|
14473
|
+
};
|
|
14474
|
+
}
|
|
14475
|
+
function computePlanProgress(dir, plan) {
|
|
14476
|
+
const epics = epicsInPlan(dir, plan.id);
|
|
14477
|
+
const byStatus = Object.fromEntries(EPIC_STATES2.map((s) => [s, 0]));
|
|
14478
|
+
for (const e of epics) byStatus[e.status]++;
|
|
14479
|
+
const inis = /* @__PURE__ */ new Map();
|
|
14480
|
+
const wis = /* @__PURE__ */ new Map();
|
|
14481
|
+
for (const e of epics) {
|
|
14482
|
+
for (const i of initiativesInEpic(dir, e.id)) inis.set(i.id, i);
|
|
14483
|
+
for (const w3 of workItemsInEpic(dir, e.id)) wis.set(w3.id, w3);
|
|
14484
|
+
}
|
|
14485
|
+
const candidates = [...inis.values()].reduce((n, i) => n + i.candidates.length, 0);
|
|
14486
|
+
const materialized = [...inis.values()].reduce((n, i) => n + i.candidates.filter((c) => c.materializedAs).length, 0);
|
|
14487
|
+
const delivery = deliveryOf([...wis.values()], dir);
|
|
14488
|
+
return {
|
|
14489
|
+
plan: plan.id,
|
|
14490
|
+
status: plan.status,
|
|
14491
|
+
epics: { total: epics.length, byStatus },
|
|
14492
|
+
planning: { initiatives: inis.size, candidates, materialized, remaining: Math.max(0, candidates - materialized) },
|
|
14493
|
+
delivery,
|
|
14494
|
+
readiness: readinessOf(plan.successCriteria.length, unmetDependencies(dir, plan.dependsOn), delivery),
|
|
14495
|
+
milestones: plan.milestones.map((m2) => ({ id: m2.id, title: m2.title, exitCriteria: m2.exitCriteria.length }))
|
|
14496
|
+
};
|
|
14497
|
+
}
|
|
14498
|
+
function unmetDependencies(dir, deps) {
|
|
14499
|
+
if (deps.length === 0) return [];
|
|
14500
|
+
const done = /* @__PURE__ */ new Set();
|
|
14501
|
+
for (const e of discoverEpics2(dir)) if (e.status === "completed") done.add(e.id.toLowerCase());
|
|
14502
|
+
for (const p of discoverDeliveryPlans2(dir)) if (p.status === "completed") done.add(p.id.toLowerCase());
|
|
14503
|
+
for (const i of discoverInitiatives2(dir)) if (i.status === "completed") done.add(i.id.toLowerCase());
|
|
14504
|
+
return deps.filter((d3) => !done.has(d3.toLowerCase()));
|
|
14505
|
+
}
|
|
14506
|
+
function findCycle(nodes, edges) {
|
|
14507
|
+
const state = /* @__PURE__ */ new Map();
|
|
14508
|
+
const stack = [];
|
|
14509
|
+
const dfs = (n) => {
|
|
14510
|
+
state.set(n, 1);
|
|
14511
|
+
stack.push(n);
|
|
14512
|
+
for (const m2 of edges.get(n) ?? []) {
|
|
14513
|
+
if (!nodes.includes(m2)) continue;
|
|
14514
|
+
const s = state.get(m2) ?? 0;
|
|
14515
|
+
if (s === 1) return [...stack.slice(stack.indexOf(m2)), m2];
|
|
14516
|
+
if (s === 0) {
|
|
14517
|
+
const c = dfs(m2);
|
|
14518
|
+
if (c) return c;
|
|
14519
|
+
}
|
|
14520
|
+
}
|
|
14521
|
+
stack.pop();
|
|
14522
|
+
state.set(n, 2);
|
|
14523
|
+
return null;
|
|
14524
|
+
};
|
|
14525
|
+
for (const n of nodes) if ((state.get(n) ?? 0) === 0) {
|
|
14526
|
+
const c = dfs(n);
|
|
14527
|
+
if (c) return c;
|
|
14528
|
+
}
|
|
14529
|
+
return null;
|
|
14530
|
+
}
|
|
14531
|
+
function validatePlanningGraph(dir) {
|
|
14532
|
+
const plans = discoverDeliveryPlans2(dir);
|
|
14533
|
+
const epics = discoverEpics2(dir);
|
|
14534
|
+
const inis = discoverInitiatives2(dir);
|
|
14535
|
+
const planIds = new Set(plans.map((p) => p.id.toLowerCase()));
|
|
14536
|
+
const epicIds = new Set(epics.map((e) => e.id.toLowerCase()));
|
|
14537
|
+
const iniIds = new Set(inis.map((i) => i.id.toLowerCase()));
|
|
14538
|
+
const findings = [];
|
|
14539
|
+
for (const p of plans) {
|
|
14540
|
+
const seen = /* @__PURE__ */ new Set();
|
|
14541
|
+
for (const m2 of p.milestones) {
|
|
14542
|
+
const key = m2.id.toLowerCase();
|
|
14543
|
+
if (seen.has(key))
|
|
14544
|
+
findings.push({ level: "error", kind: "duplicate_milestone", subject: p.id, message: `Plan ${p.id} declares milestone ${m2.id} more than once.` });
|
|
14545
|
+
seen.add(key);
|
|
14546
|
+
}
|
|
14547
|
+
}
|
|
14548
|
+
for (const e of epics) {
|
|
14549
|
+
if (e.plan && !planIds.has(e.plan.toLowerCase()))
|
|
14550
|
+
findings.push({ level: "error", kind: "invalid_reference", subject: e.id, message: `Epic ${e.id} references missing plan ${e.plan}.` });
|
|
14551
|
+
if (e.plan && e.milestone) {
|
|
14552
|
+
const plan = plans.find((p) => p.id.toLowerCase() === e.plan.toLowerCase());
|
|
14553
|
+
if (plan && !plan.milestones.some((m2) => m2.id.toLowerCase() === e.milestone.toLowerCase()))
|
|
14554
|
+
findings.push({ level: "error", kind: "invalid_reference", subject: e.id, message: `Epic ${e.id} references milestone ${e.milestone} not defined in ${e.plan}.` });
|
|
14555
|
+
}
|
|
14556
|
+
for (const d3 of e.dependsOn) if (!epicIds.has(d3.toLowerCase()))
|
|
14557
|
+
findings.push({ level: "error", kind: "invalid_reference", subject: e.id, message: `Epic ${e.id} depends on missing Epic ${d3}.` });
|
|
14558
|
+
}
|
|
14559
|
+
for (const i of inis) {
|
|
14560
|
+
if (i.epic && !epicIds.has(i.epic.toLowerCase()))
|
|
14561
|
+
findings.push({ level: "error", kind: "invalid_reference", subject: i.id, message: `Initiative ${i.id} references missing Epic ${i.epic}.` });
|
|
14562
|
+
}
|
|
14563
|
+
const epicEdges = new Map(epics.map((e) => [e.id.toLowerCase(), e.dependsOn.map((d3) => d3.toLowerCase())]));
|
|
14564
|
+
const epicCycle = findCycle([...epicIds], epicEdges);
|
|
14565
|
+
if (epicCycle) findings.push({ level: "error", kind: "dependency_cycle", subject: epicCycle[0], message: `Dependency cycle among Epics: ${epicCycle.join(" \u2192 ")}.` });
|
|
14566
|
+
for (const e of epics) for (const d3 of unmetDependencies(dir, e.dependsOn))
|
|
14567
|
+
findings.push({ level: "warning", kind: "pending_dependency", subject: e.id, message: `Epic ${e.id} is blocked by unfinished dependency ${d3}.` });
|
|
14568
|
+
return findings;
|
|
14569
|
+
}
|
|
14142
14570
|
var RESOURCE_ARTIFACT_TYPE2 = "project-resource";
|
|
14143
14571
|
var REDACTED_COORDINATE2 = "\u2022\u2022\u2022\u2022 (hidden \u2014 store as an authentication reference)";
|
|
14144
14572
|
var SECRET_KEY_RE2 = /(pass(word|phrase)?|secret|token|api[_-]?key|\bkey\b|credential|private|auth[_-]?token|access[_-]?key)/i;
|
|
@@ -14242,7 +14670,7 @@ function purposeFromBody3(body) {
|
|
|
14242
14670
|
return text22 || null;
|
|
14243
14671
|
}
|
|
14244
14672
|
function toDetail2(filePath, relPath, raw) {
|
|
14245
|
-
const parsed =
|
|
14673
|
+
const parsed = matter52(raw);
|
|
14246
14674
|
const fm = parsed.data ?? {};
|
|
14247
14675
|
const body = parsed.content.trim();
|
|
14248
14676
|
const id = str3(fm.id) ?? "";
|
|
@@ -14458,7 +14886,7 @@ function getDeprecationPlan2(dir, id) {
|
|
|
14458
14886
|
);
|
|
14459
14887
|
if (!match) return null;
|
|
14460
14888
|
const raw = readFile2(match.filePath);
|
|
14461
|
-
const parsed =
|
|
14889
|
+
const parsed = matter62(raw);
|
|
14462
14890
|
const fm = parsed.data ?? {};
|
|
14463
14891
|
const body = parsed.content.trim();
|
|
14464
14892
|
const targets = parseTargets2(fm.targets);
|
|
@@ -14486,14 +14914,14 @@ var ALL_STATUSES2 = ["draft", "ready", "in-progress", "blocked", "completed"];
|
|
|
14486
14914
|
function scopeStatuses2(scope) {
|
|
14487
14915
|
return scope === "all" ? { included: ALL_STATUSES2, excluded: ["archived"] } : { included: ACTIVE_STATUSES2, excluded: ["completed", "archived"] };
|
|
14488
14916
|
}
|
|
14489
|
-
function
|
|
14917
|
+
function toPosix42(p) {
|
|
14490
14918
|
return p.replace(/\\/g, "/");
|
|
14491
14919
|
}
|
|
14492
14920
|
function slug3(s) {
|
|
14493
14921
|
return s.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
14494
14922
|
}
|
|
14495
14923
|
function isAdr3(a2) {
|
|
14496
|
-
return
|
|
14924
|
+
return toPosix42(a2.filePath).includes("/tech/decisions/") && Boolean(a2.type);
|
|
14497
14925
|
}
|
|
14498
14926
|
function buildGraph2(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
|
|
14499
14927
|
const scope = opts.scope ?? "active";
|
|
@@ -14604,6 +15032,27 @@ function buildGraph2(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
|
|
|
14604
15032
|
}
|
|
14605
15033
|
}
|
|
14606
15034
|
}
|
|
15035
|
+
for (const plan of discoverDeliveryPlans2(dir)) {
|
|
15036
|
+
const planId = `delivery-plan:${slug3(plan.id) || plan.id}`;
|
|
15037
|
+
addNode({ id: planId, type: "delivery-plan", label: `${plan.id} ${plan.title}`.trim(), path: plan.relPath, status: plan.status });
|
|
15038
|
+
for (const m2 of plan.milestones) {
|
|
15039
|
+
const milestoneId = `milestone:${slug3(plan.id)}:${slug3(m2.id) || m2.id}`;
|
|
15040
|
+
addNode({ id: milestoneId, type: "milestone", label: `${m2.id} ${m2.title}`.trim(), path: plan.relPath });
|
|
15041
|
+
addEdge(milestoneId, planId, "belongs_to");
|
|
15042
|
+
}
|
|
15043
|
+
}
|
|
15044
|
+
for (const epic of discoverEpics2(dir)) {
|
|
15045
|
+
const epicNodeId = `epic:${slug3(epic.id) || epic.id}`;
|
|
15046
|
+
addNode({ id: epicNodeId, type: "epic", label: `${epic.id} ${epic.title}`.trim(), path: epic.relPath, status: epic.status });
|
|
15047
|
+
if (epic.plan) {
|
|
15048
|
+
addEdge(epicNodeId, `delivery-plan:${slug3(epic.plan)}`, "belongs_to");
|
|
15049
|
+
if (epic.milestone) addEdge(epicNodeId, `milestone:${slug3(epic.plan)}:${slug3(epic.milestone)}`, "belongs_to");
|
|
15050
|
+
}
|
|
15051
|
+
for (const dep of epic.dependsOn) addEdge(epicNodeId, `epic:${slug3(dep)}`, "depends_on");
|
|
15052
|
+
}
|
|
15053
|
+
for (const ini of discoverInitiatives2(dir)) {
|
|
15054
|
+
if (ini.epic) addEdge(`initiative:${slug3(ini.id) || ini.id}`, `epic:${slug3(ini.epic)}`, "belongs_to");
|
|
15055
|
+
}
|
|
14607
15056
|
for (const wi of selectedWIs) {
|
|
14608
15057
|
const id = wi.id || wi.title;
|
|
14609
15058
|
if (!id || !id.trim()) continue;
|
|
@@ -14708,14 +15157,14 @@ function buildGraph2(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
|
|
|
14708
15157
|
};
|
|
14709
15158
|
}
|
|
14710
15159
|
var KNOWLEDGE32 = "knowledge";
|
|
14711
|
-
function
|
|
15160
|
+
function toPosix52(p) {
|
|
14712
15161
|
return p.replace(/\\/g, "/");
|
|
14713
15162
|
}
|
|
14714
15163
|
function slug22(s) {
|
|
14715
15164
|
return s.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
14716
15165
|
}
|
|
14717
15166
|
function isAdr22(a2) {
|
|
14718
|
-
return
|
|
15167
|
+
return toPosix52(a2.filePath).includes("/tech/decisions/") && Boolean(a2.type);
|
|
14719
15168
|
}
|
|
14720
15169
|
function capabilityHeadings2(dir) {
|
|
14721
15170
|
const p = join3(dir, KNOWLEDGE32, "product", "capabilities.md");
|
|
@@ -15048,7 +15497,7 @@ function reportFiles2(dir) {
|
|
|
15048
15497
|
const match = name.match(reportName2);
|
|
15049
15498
|
if (!match) return [];
|
|
15050
15499
|
try {
|
|
15051
|
-
const parsed =
|
|
15500
|
+
const parsed = matter82(readFile2(join3(delivery, name)));
|
|
15052
15501
|
const fingerprint = typeof parsed.data.source_fingerprint === "string" ? parsed.data.source_fingerprint : "";
|
|
15053
15502
|
const manifest = parsed.data.source_manifest;
|
|
15054
15503
|
const sourceManifest = manifest && typeof manifest === "object" && !Array.isArray(manifest) ? Object.fromEntries(Object.entries(manifest).filter(([, digest]) => typeof digest === "string")) : {};
|
|
@@ -15147,7 +15596,7 @@ function canonicalReport(context, proposal) {
|
|
|
15147
15596
|
source_manifest: Object.fromEntries(context.sources.map((item) => [item.path, item.digest]))
|
|
15148
15597
|
};
|
|
15149
15598
|
if (supersedes) data.supersedes = supersedes;
|
|
15150
|
-
return
|
|
15599
|
+
return matter82.stringify(redactSecrets2(proposal).trimEnd() + "\n", data);
|
|
15151
15600
|
}
|
|
15152
15601
|
function persistPocReport(dir, proposal, opts = {}) {
|
|
15153
15602
|
const context = buildPocReportContext2(dir);
|
|
@@ -16111,9 +16560,9 @@ var RESPONSIBILITY_MATRIX2 = {
|
|
|
16111
16560
|
},
|
|
16112
16561
|
"roadmap-agent": {
|
|
16113
16562
|
agent: "roadmap-agent",
|
|
16114
|
-
responsibleFor: ["Roadmap", "Initiatives", "Work Item candidates"],
|
|
16563
|
+
responsibleFor: ["Roadmap", "Initiatives", "Work Item candidates", "Delivery Plans and Epics (optional planning layer)"],
|
|
16115
16564
|
produces: ["knowledge/delivery/roadmap.md"],
|
|
16116
|
-
canSuggest: ["kaddo create --from roadmap", "work-item-agent"],
|
|
16565
|
+
canSuggest: ["kaddo create --from roadmap", "work-item-agent", "kaddo plan create", "kaddo epic create"],
|
|
16117
16566
|
cannotSuggest: ["branches", "commits", "pull requests", "code"],
|
|
16118
16567
|
next: ["kaddo create --from roadmap", "work-item-agent"]
|
|
16119
16568
|
},
|
|
@@ -18616,19 +19065,19 @@ function parseWorkItemSource2(frontmatter2) {
|
|
|
18616
19065
|
const rawSource = frontmatter2.source;
|
|
18617
19066
|
if (rawSource != null && typeof rawSource === "object" && !Array.isArray(rawSource)) {
|
|
18618
19067
|
const obj = rawSource;
|
|
18619
|
-
const t =
|
|
19068
|
+
const t = optStr32(obj.type);
|
|
18620
19069
|
const type = t && isValidSource2(t) ? t : "unknown";
|
|
18621
19070
|
return {
|
|
18622
19071
|
type,
|
|
18623
|
-
id:
|
|
18624
|
-
title:
|
|
18625
|
-
context:
|
|
18626
|
-
provider:
|
|
18627
|
-
integration:
|
|
18628
|
-
url:
|
|
18629
|
-
imported_at:
|
|
18630
|
-
external_updated_at:
|
|
18631
|
-
synced_at:
|
|
19072
|
+
id: optStr32(obj.id) ?? optStr32(frontmatter2.source_id),
|
|
19073
|
+
title: optStr32(obj.title) ?? optStr32(frontmatter2.source_title),
|
|
19074
|
+
context: optStr32(obj.context) ?? optStr32(frontmatter2.source_context),
|
|
19075
|
+
provider: optStr32(obj.provider) ?? optStr32(frontmatter2.source_provider),
|
|
19076
|
+
integration: optStr32(obj.integration) ?? optStr32(frontmatter2.source_integration),
|
|
19077
|
+
url: optStr32(obj.url) ?? optStr32(frontmatter2.source_url),
|
|
19078
|
+
imported_at: optStr32(obj.imported_at) ?? optStr32(frontmatter2.source_imported_at),
|
|
19079
|
+
external_updated_at: optStr32(obj.external_updated_at) ?? optStr32(frontmatter2.source_external_updated_at),
|
|
19080
|
+
synced_at: optStr32(obj.synced_at) ?? optStr32(frontmatter2.source_synced_at),
|
|
18632
19081
|
inferred: obj.inferred === true || obj.inferred === "true",
|
|
18633
19082
|
reason: t && !isValidSource2(t) ? `Invalid source type in object: "${t}".` : void 0
|
|
18634
19083
|
};
|
|
@@ -18637,28 +19086,28 @@ function parseWorkItemSource2(frontmatter2) {
|
|
|
18637
19086
|
if (raw && isValidSource2(raw)) {
|
|
18638
19087
|
return {
|
|
18639
19088
|
type: raw,
|
|
18640
|
-
id:
|
|
18641
|
-
title:
|
|
18642
|
-
context:
|
|
18643
|
-
provider:
|
|
18644
|
-
url:
|
|
18645
|
-
imported_at:
|
|
18646
|
-
external_updated_at:
|
|
18647
|
-
synced_at:
|
|
19089
|
+
id: optStr32(frontmatter2.source_id),
|
|
19090
|
+
title: optStr32(frontmatter2.source_title),
|
|
19091
|
+
context: optStr32(frontmatter2.source_context),
|
|
19092
|
+
provider: optStr32(frontmatter2.source_provider),
|
|
19093
|
+
url: optStr32(frontmatter2.source_url),
|
|
19094
|
+
imported_at: optStr32(frontmatter2.source_imported_at),
|
|
19095
|
+
external_updated_at: optStr32(frontmatter2.source_external_updated_at),
|
|
19096
|
+
synced_at: optStr32(frontmatter2.source_synced_at),
|
|
18648
19097
|
inferred: false
|
|
18649
19098
|
};
|
|
18650
19099
|
}
|
|
18651
19100
|
if (raw && !isValidSource2(raw)) {
|
|
18652
19101
|
return {
|
|
18653
19102
|
type: "unknown",
|
|
18654
|
-
id:
|
|
18655
|
-
title:
|
|
18656
|
-
context:
|
|
18657
|
-
provider:
|
|
18658
|
-
url:
|
|
18659
|
-
imported_at:
|
|
18660
|
-
external_updated_at:
|
|
18661
|
-
synced_at:
|
|
19103
|
+
id: optStr32(frontmatter2.source_id),
|
|
19104
|
+
title: optStr32(frontmatter2.source_title),
|
|
19105
|
+
context: optStr32(frontmatter2.source_context),
|
|
19106
|
+
provider: optStr32(frontmatter2.source_provider),
|
|
19107
|
+
url: optStr32(frontmatter2.source_url),
|
|
19108
|
+
imported_at: optStr32(frontmatter2.source_imported_at),
|
|
19109
|
+
external_updated_at: optStr32(frontmatter2.source_external_updated_at),
|
|
19110
|
+
synced_at: optStr32(frontmatter2.source_synced_at),
|
|
18662
19111
|
inferred: true,
|
|
18663
19112
|
reason: `Invalid source value: "${raw}".`
|
|
18664
19113
|
};
|
|
@@ -18666,8 +19115,8 @@ function parseWorkItemSource2(frontmatter2) {
|
|
|
18666
19115
|
if (frontmatter2.source_work_item_candidate || frontmatter2.source_roadmap_initiative) {
|
|
18667
19116
|
return {
|
|
18668
19117
|
type: "roadmap",
|
|
18669
|
-
id:
|
|
18670
|
-
title:
|
|
19118
|
+
id: optStr32(frontmatter2.source_id) ?? optStr32(frontmatter2.source_work_item_candidate),
|
|
19119
|
+
title: optStr32(frontmatter2.source_initiative_title),
|
|
18671
19120
|
inferred: true,
|
|
18672
19121
|
reason: "Inferred from legacy roadmap fields."
|
|
18673
19122
|
};
|
|
@@ -18675,7 +19124,7 @@ function parseWorkItemSource2(frontmatter2) {
|
|
|
18675
19124
|
if (frontmatter2.source_id) {
|
|
18676
19125
|
return {
|
|
18677
19126
|
type: "unknown",
|
|
18678
|
-
id:
|
|
19127
|
+
id: optStr32(frontmatter2.source_id),
|
|
18679
19128
|
inferred: true,
|
|
18680
19129
|
reason: "Has source_id but no source type."
|
|
18681
19130
|
};
|
|
@@ -18686,7 +19135,7 @@ function parseWorkItemSource2(frontmatter2) {
|
|
|
18686
19135
|
reason: "No source metadata found."
|
|
18687
19136
|
};
|
|
18688
19137
|
}
|
|
18689
|
-
function
|
|
19138
|
+
function optStr32(v) {
|
|
18690
19139
|
if (typeof v === "string" && v.trim()) return v.trim();
|
|
18691
19140
|
return void 0;
|
|
18692
19141
|
}
|
|
@@ -18933,17 +19382,387 @@ var LEGACY_STEPS2 = [
|
|
|
18933
19382
|
var HISTORY_DIR2 = ".kaddo/history";
|
|
18934
19383
|
var RUNS_PATH2 = `${HISTORY_DIR2}/guard-runs.jsonl`;
|
|
18935
19384
|
var SUMMARY_PATH2 = `${HISTORY_DIR2}/guard-summary.json`;
|
|
18936
|
-
var
|
|
18937
|
-
|
|
18938
|
-
|
|
18939
|
-
|
|
18940
|
-
|
|
18941
|
-
|
|
18942
|
-
|
|
18943
|
-
|
|
18944
|
-
|
|
18945
|
-
"
|
|
18946
|
-
|
|
19385
|
+
var WORK_ITEMS_DIR = "knowledge/delivery/work-items";
|
|
19386
|
+
var DRAFT_DIR = `${WORK_ITEMS_DIR}/draft`;
|
|
19387
|
+
var InitiativeWriteError = class extends Error {
|
|
19388
|
+
constructor(message) {
|
|
19389
|
+
super(message);
|
|
19390
|
+
this.name = "InitiativeWriteError";
|
|
19391
|
+
}
|
|
19392
|
+
};
|
|
19393
|
+
function slugify22(s) {
|
|
19394
|
+
return s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 50);
|
|
19395
|
+
}
|
|
19396
|
+
function today2() {
|
|
19397
|
+
return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
19398
|
+
}
|
|
19399
|
+
function candidateToData(c) {
|
|
19400
|
+
const out = { id: c.id, title: c.title };
|
|
19401
|
+
if (c.type) out.type = c.type;
|
|
19402
|
+
if (c.suggestedKnowledgeLevel) out.suggested_knowledge_level = c.suggestedKnowledgeLevel;
|
|
19403
|
+
if (c.expectedValue) out.expected_value = c.expectedValue;
|
|
19404
|
+
if (c.notes) out.notes = c.notes;
|
|
19405
|
+
if (c.sourceSignals.length) out.source_signals = c.sourceSignals;
|
|
19406
|
+
if (c.dependencies.length) out.dependencies = c.dependencies;
|
|
19407
|
+
if (c.materializedAs) out.materialized_as = c.materializedAs;
|
|
19408
|
+
return out;
|
|
19409
|
+
}
|
|
19410
|
+
function externalLinkToData(l2) {
|
|
19411
|
+
const out = { integration: l2.integration, external_id: l2.externalId };
|
|
19412
|
+
if (l2.externalType) out.external_type = l2.externalType;
|
|
19413
|
+
if (l2.url) out.url = l2.url;
|
|
19414
|
+
if (l2.externalStatus) out.external_status = l2.externalStatus;
|
|
19415
|
+
return out;
|
|
19416
|
+
}
|
|
19417
|
+
function normalizeCandidate(input, fallbackId) {
|
|
19418
|
+
return {
|
|
19419
|
+
id: input.id?.trim() || fallbackId,
|
|
19420
|
+
title: input.title.trim(),
|
|
19421
|
+
type: input.type?.trim() || void 0,
|
|
19422
|
+
suggestedKnowledgeLevel: input.suggestedKnowledgeLevel?.trim() || void 0,
|
|
19423
|
+
expectedValue: input.expectedValue?.trim() || void 0,
|
|
19424
|
+
notes: input.notes?.trim() || void 0,
|
|
19425
|
+
sourceSignals: input.sourceSignals ?? [],
|
|
19426
|
+
dependencies: input.dependencies ?? [],
|
|
19427
|
+
materializedAs: input.materializedAs ?? null
|
|
19428
|
+
};
|
|
19429
|
+
}
|
|
19430
|
+
function defaultBody(title) {
|
|
19431
|
+
return [
|
|
19432
|
+
`# ${title}`,
|
|
19433
|
+
"",
|
|
19434
|
+
"## Goal",
|
|
19435
|
+
"",
|
|
19436
|
+
"_What outcome does this Initiative pursue?_",
|
|
19437
|
+
"",
|
|
19438
|
+
"## Expected Value",
|
|
19439
|
+
"",
|
|
19440
|
+
"_Why it matters._",
|
|
19441
|
+
"",
|
|
19442
|
+
"## Scope",
|
|
19443
|
+
"",
|
|
19444
|
+
"_What this Initiative covers._",
|
|
19445
|
+
"",
|
|
19446
|
+
"## Out of Scope",
|
|
19447
|
+
"",
|
|
19448
|
+
"_What it explicitly does not cover._",
|
|
19449
|
+
"",
|
|
19450
|
+
"## Success Criteria",
|
|
19451
|
+
"",
|
|
19452
|
+
'_Observable outcomes that define completion (not just "all Work Items done")._',
|
|
19453
|
+
"",
|
|
19454
|
+
"## Dependencies",
|
|
19455
|
+
"",
|
|
19456
|
+
"_Other Initiatives, modules, or external work this depends on._",
|
|
19457
|
+
"",
|
|
19458
|
+
"## Work Item Candidates",
|
|
19459
|
+
"",
|
|
19460
|
+
"_Candidates live in frontmatter `candidates`; summarize them here as they evolve._",
|
|
19461
|
+
"",
|
|
19462
|
+
"## Open Questions",
|
|
19463
|
+
"",
|
|
19464
|
+
"_Unresolved questions for this Initiative._",
|
|
19465
|
+
"",
|
|
19466
|
+
"## Learning",
|
|
19467
|
+
"",
|
|
19468
|
+
"_Captured on completion: what was delivered, what stayed out of scope, outcome reached._",
|
|
19469
|
+
""
|
|
19470
|
+
].join("\n");
|
|
19471
|
+
}
|
|
19472
|
+
function buildInitiativeContent(id, input) {
|
|
19473
|
+
const status = input.status ?? "planned";
|
|
19474
|
+
if (!isValidInitiativeStatus2(status)) {
|
|
19475
|
+
throw new InitiativeWriteError(`Invalid initiative status: ${status}.`);
|
|
19476
|
+
}
|
|
19477
|
+
const candidates = (input.candidates ?? []).map(
|
|
19478
|
+
(c, i) => normalizeCandidate(c, `WI-CANDIDATE-${String(i + 1).padStart(3, "0")}`)
|
|
19479
|
+
);
|
|
19480
|
+
const data = {
|
|
19481
|
+
type: "initiative",
|
|
19482
|
+
id,
|
|
19483
|
+
title: input.title.trim(),
|
|
19484
|
+
status,
|
|
19485
|
+
knowledge_level: input.knowledgeLevel ?? "K2",
|
|
19486
|
+
domains: input.domains ?? [],
|
|
19487
|
+
related_capabilities: input.relatedCapabilities ?? [],
|
|
19488
|
+
created_at: today2(),
|
|
19489
|
+
external_links: (input.externalLinks ?? []).map(externalLinkToData),
|
|
19490
|
+
candidates: candidates.map(candidateToData)
|
|
19491
|
+
};
|
|
19492
|
+
if (input.horizon) data.horizon = input.horizon;
|
|
19493
|
+
if (input.priority) data.priority = input.priority;
|
|
19494
|
+
if (input.source) data.source = input.source;
|
|
19495
|
+
if (input.sourceId) data.source_id = input.sourceId;
|
|
19496
|
+
if (input.epic) data.epic = input.epic.trim();
|
|
19497
|
+
if (input.owners?.length) data.owners = input.owners.map((o2) => o2.trim()).filter(Boolean);
|
|
19498
|
+
if (input.contributors?.length) data.contributors = input.contributors.map((c) => c.trim()).filter(Boolean);
|
|
19499
|
+
return matter122.stringify("\n" + (input.body ?? defaultBody(input.title.trim())), data);
|
|
19500
|
+
}
|
|
19501
|
+
function createInitiative(dir, input) {
|
|
19502
|
+
if (!input.title?.trim()) throw new InitiativeWriteError("Initiative title is required.");
|
|
19503
|
+
const id = nextInitiativeId(dir);
|
|
19504
|
+
const fileName = `${id}-${slugify22(input.title)}.md`;
|
|
19505
|
+
const iniDir = join3(dir, INITIATIVES_DIR2);
|
|
19506
|
+
ensureDir2(iniDir);
|
|
19507
|
+
const filePath = join3(iniDir, fileName);
|
|
19508
|
+
const content = buildInitiativeContent(id, input);
|
|
19509
|
+
writeFile2(filePath, content);
|
|
19510
|
+
const ini = parseInitiative2(dir, filePath, content);
|
|
19511
|
+
if (!ini) throw new InitiativeWriteError("Failed to parse the Initiative just written.");
|
|
19512
|
+
return ini;
|
|
19513
|
+
}
|
|
19514
|
+
function transitionInitiative(dir, id, to) {
|
|
19515
|
+
const ini = getInitiative(dir, id);
|
|
19516
|
+
if (!ini) throw new InitiativeWriteError(`Initiative ${id} not found.`);
|
|
19517
|
+
if (!isValidInitiativeStatus2(to)) throw new InitiativeWriteError(`Invalid status: ${to}.`);
|
|
19518
|
+
if (ini.status === to) return ini;
|
|
19519
|
+
if (!isValidInitiativeTransition(ini.status, to)) {
|
|
19520
|
+
throw new InitiativeWriteError(`Invalid transition ${ini.status} \u2192 ${to}.`);
|
|
19521
|
+
}
|
|
19522
|
+
const raw = readFile2(ini.filePath);
|
|
19523
|
+
const { data, content } = matter122(raw, {});
|
|
19524
|
+
data.status = to;
|
|
19525
|
+
if (to === "completed") data.completed_at = today2();
|
|
19526
|
+
const newContent = matter122.stringify(content, data);
|
|
19527
|
+
writeFile2(ini.filePath, newContent);
|
|
19528
|
+
const updated = parseInitiative2(dir, ini.filePath, newContent);
|
|
19529
|
+
if (!updated) throw new InitiativeWriteError("Failed to parse the Initiative after transition.");
|
|
19530
|
+
return updated;
|
|
19531
|
+
}
|
|
19532
|
+
function addCandidate(dir, id, candidate) {
|
|
19533
|
+
const ini = getInitiative(dir, id);
|
|
19534
|
+
if (!ini) throw new InitiativeWriteError(`Initiative ${id} not found.`);
|
|
19535
|
+
const raw = readFile2(ini.filePath);
|
|
19536
|
+
const { data, content } = matter122(raw, {});
|
|
19537
|
+
const existing = Array.isArray(data.candidates) ? data.candidates : [];
|
|
19538
|
+
const fallbackId = `WI-CANDIDATE-${String(existing.length + 1).padStart(3, "0")}`;
|
|
19539
|
+
const normalized = normalizeCandidate(candidate, fallbackId);
|
|
19540
|
+
if (ini.candidates.some((c) => c.id === normalized.id)) {
|
|
19541
|
+
throw new InitiativeWriteError(`Candidate ${normalized.id} already exists in ${id}.`);
|
|
19542
|
+
}
|
|
19543
|
+
data.candidates = [...existing, candidateToData(normalized)];
|
|
19544
|
+
const newContent = matter122.stringify(content, data);
|
|
19545
|
+
writeFile2(ini.filePath, newContent);
|
|
19546
|
+
const updated = parseInitiative2(dir, ini.filePath, newContent);
|
|
19547
|
+
if (!updated) throw new InitiativeWriteError("Failed to parse the Initiative after adding a candidate.");
|
|
19548
|
+
return updated;
|
|
19549
|
+
}
|
|
19550
|
+
function updateInitiative(dir, id, patch) {
|
|
19551
|
+
const ini = getInitiative(dir, id);
|
|
19552
|
+
if (!ini) throw new InitiativeWriteError(`Initiative ${id} not found.`);
|
|
19553
|
+
const raw = readFile2(ini.filePath);
|
|
19554
|
+
const { data, content } = matter122(raw, {});
|
|
19555
|
+
if (patch.title !== void 0) {
|
|
19556
|
+
if (!patch.title.trim()) throw new InitiativeWriteError("Initiative title cannot be empty.");
|
|
19557
|
+
data.title = patch.title.trim();
|
|
19558
|
+
}
|
|
19559
|
+
if (patch.horizon !== void 0) data.horizon = patch.horizon;
|
|
19560
|
+
if (patch.priority !== void 0) data.priority = patch.priority;
|
|
19561
|
+
if (patch.knowledgeLevel !== void 0) data.knowledge_level = patch.knowledgeLevel;
|
|
19562
|
+
if (patch.domains !== void 0) data.domains = patch.domains;
|
|
19563
|
+
if (patch.relatedCapabilities !== void 0) data.related_capabilities = patch.relatedCapabilities;
|
|
19564
|
+
if (patch.epic !== void 0) {
|
|
19565
|
+
if (patch.epic === null || patch.epic.trim() === "") delete data.epic;
|
|
19566
|
+
else data.epic = patch.epic.trim();
|
|
19567
|
+
}
|
|
19568
|
+
if (patch.owners !== void 0) data.owners = patch.owners.map((o2) => o2.trim()).filter(Boolean);
|
|
19569
|
+
if (patch.contributors !== void 0) data.contributors = patch.contributors.map((c) => c.trim()).filter(Boolean);
|
|
19570
|
+
const newContent = matter122.stringify(content, data);
|
|
19571
|
+
writeFile2(ini.filePath, newContent);
|
|
19572
|
+
const updated = parseInitiative2(dir, ini.filePath, newContent);
|
|
19573
|
+
if (!updated) throw new InitiativeWriteError("Failed to parse the Initiative after update.");
|
|
19574
|
+
return updated;
|
|
19575
|
+
}
|
|
19576
|
+
function addExternalLink(dir, id, link) {
|
|
19577
|
+
const ini = getInitiative(dir, id);
|
|
19578
|
+
if (!ini) throw new InitiativeWriteError(`Initiative ${id} not found.`);
|
|
19579
|
+
if (!link.integration?.trim() || !link.externalId?.trim()) {
|
|
19580
|
+
throw new InitiativeWriteError("External link requires integration and externalId.");
|
|
19581
|
+
}
|
|
19582
|
+
if (ini.externalLinks.some(
|
|
19583
|
+
(l2) => l2.integration.trim().toLowerCase() === link.integration.trim().toLowerCase() && l2.externalId.trim().toLowerCase() === link.externalId.trim().toLowerCase()
|
|
19584
|
+
)) {
|
|
19585
|
+
throw new InitiativeWriteError(`External link ${link.integration}:${link.externalId} already exists on ${id}.`);
|
|
19586
|
+
}
|
|
19587
|
+
const raw = readFile2(ini.filePath);
|
|
19588
|
+
const { data, content } = matter122(raw, {});
|
|
19589
|
+
const existing = Array.isArray(data.external_links) ? data.external_links : [];
|
|
19590
|
+
data.external_links = [...existing, externalLinkToData(link)];
|
|
19591
|
+
const newContent = matter122.stringify(content, data);
|
|
19592
|
+
writeFile2(ini.filePath, newContent);
|
|
19593
|
+
const updated = parseInitiative2(dir, ini.filePath, newContent);
|
|
19594
|
+
if (!updated) throw new InitiativeWriteError("Failed to parse the Initiative after adding an external link.");
|
|
19595
|
+
return updated;
|
|
19596
|
+
}
|
|
19597
|
+
function nextWorkItemId(dir) {
|
|
19598
|
+
const wiDir = join3(dir, WORK_ITEMS_DIR);
|
|
19599
|
+
let max = 0;
|
|
19600
|
+
const walk2 = (d3) => {
|
|
19601
|
+
if (!exists2(d3)) return;
|
|
19602
|
+
for (const entry of readDir2(d3)) {
|
|
19603
|
+
const full = join3(d3, entry);
|
|
19604
|
+
if (isFile2(full)) {
|
|
19605
|
+
const m2 = entry.match(/WI-(\d+)/);
|
|
19606
|
+
if (m2) max = Math.max(max, parseInt(m2[1], 10));
|
|
19607
|
+
} else if (!entry.startsWith(".")) {
|
|
19608
|
+
walk2(full);
|
|
19609
|
+
}
|
|
19610
|
+
}
|
|
19611
|
+
};
|
|
19612
|
+
walk2(wiDir);
|
|
19613
|
+
return `WI-${String(max + 1).padStart(3, "0")}`;
|
|
19614
|
+
}
|
|
19615
|
+
function buildWorkItemContent(wiId, iniId, candidate) {
|
|
19616
|
+
const data = {
|
|
19617
|
+
type: candidate.type ?? "feature",
|
|
19618
|
+
id: wiId,
|
|
19619
|
+
title: candidate.title,
|
|
19620
|
+
knowledge_level: candidate.suggestedKnowledgeLevel ?? "K2",
|
|
19621
|
+
status: "draft",
|
|
19622
|
+
phase: "now",
|
|
19623
|
+
initiative: iniId,
|
|
19624
|
+
domains: [],
|
|
19625
|
+
code: [],
|
|
19626
|
+
created_at: today2(),
|
|
19627
|
+
source: "initiative",
|
|
19628
|
+
source_id: candidate.id,
|
|
19629
|
+
source_initiative: iniId,
|
|
19630
|
+
affected_modules: []
|
|
19631
|
+
};
|
|
19632
|
+
if (candidate.sourceSignals.length) data.source_signals = candidate.sourceSignals;
|
|
19633
|
+
if (candidate.dependencies.length) data.dependencies = candidate.dependencies;
|
|
19634
|
+
const body = [
|
|
19635
|
+
`# ${candidate.title}`,
|
|
19636
|
+
"",
|
|
19637
|
+
`> Materialized from Initiative ${iniId}, candidate ${candidate.id}.`,
|
|
19638
|
+
"",
|
|
19639
|
+
"## Problem",
|
|
19640
|
+
"",
|
|
19641
|
+
candidate.expectedValue ? `_Expected value:_ ${candidate.expectedValue}` : "_Describe the problem._",
|
|
19642
|
+
"",
|
|
19643
|
+
"## Acceptance Criteria",
|
|
19644
|
+
"",
|
|
19645
|
+
"- [ ] _Define at least one testable criterion._",
|
|
19646
|
+
"",
|
|
19647
|
+
"## Out of scope",
|
|
19648
|
+
"",
|
|
19649
|
+
"_Not included in this Work Item._",
|
|
19650
|
+
"",
|
|
19651
|
+
"## Validation",
|
|
19652
|
+
"",
|
|
19653
|
+
"_How will this be validated?_",
|
|
19654
|
+
"",
|
|
19655
|
+
"## Learning",
|
|
19656
|
+
"",
|
|
19657
|
+
"_What did we learn? Update after completion._",
|
|
19658
|
+
""
|
|
19659
|
+
].join("\n");
|
|
19660
|
+
return matter122.stringify("\n" + body, data);
|
|
19661
|
+
}
|
|
19662
|
+
function materializeCandidate(dir, id, candidateId) {
|
|
19663
|
+
const ini = getInitiative(dir, id);
|
|
19664
|
+
if (!ini) throw new InitiativeWriteError(`Initiative ${id} not found.`);
|
|
19665
|
+
const candidate = ini.candidates.find((c) => c.id === candidateId);
|
|
19666
|
+
if (!candidate) throw new InitiativeWriteError(`Candidate ${candidateId} not found in ${id}.`);
|
|
19667
|
+
if (candidate.materializedAs) {
|
|
19668
|
+
throw new InitiativeWriteError(
|
|
19669
|
+
`Candidate ${candidateId} is already materialized as ${candidate.materializedAs}.`
|
|
19670
|
+
);
|
|
19671
|
+
}
|
|
19672
|
+
const wiId = nextWorkItemId(dir);
|
|
19673
|
+
const fileName = `${wiId}-${slugify22(candidate.title)}.md`;
|
|
19674
|
+
ensureDir2(join3(dir, DRAFT_DIR));
|
|
19675
|
+
writeFile2(join3(dir, DRAFT_DIR, fileName), buildWorkItemContent(wiId, ini.id, candidate));
|
|
19676
|
+
const raw = readFile2(ini.filePath);
|
|
19677
|
+
const { data, content } = matter122(raw, {});
|
|
19678
|
+
const cands = Array.isArray(data.candidates) ? data.candidates : [];
|
|
19679
|
+
for (const c of cands) {
|
|
19680
|
+
if (c && typeof c === "object" && String(c.id) === candidateId) c.materialized_as = wiId;
|
|
19681
|
+
}
|
|
19682
|
+
data.candidates = cands;
|
|
19683
|
+
const newContent = matter122.stringify(content, data);
|
|
19684
|
+
writeFile2(ini.filePath, newContent);
|
|
19685
|
+
const updated = parseInitiative2(dir, ini.filePath, newContent);
|
|
19686
|
+
if (!updated) throw new InitiativeWriteError("Failed to parse the Initiative after materialization.");
|
|
19687
|
+
return { workItemId: wiId, fileName, initiative: updated };
|
|
19688
|
+
}
|
|
19689
|
+
var DeliveryPlanningWriteError = class extends Error {
|
|
19690
|
+
constructor(message) {
|
|
19691
|
+
super(message);
|
|
19692
|
+
this.name = "DeliveryPlanningWriteError";
|
|
19693
|
+
}
|
|
19694
|
+
};
|
|
19695
|
+
function slugify3(s) {
|
|
19696
|
+
return s.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60) || "item";
|
|
19697
|
+
}
|
|
19698
|
+
function createDeliveryPlan(dir, input) {
|
|
19699
|
+
if (!input.title?.trim()) throw new DeliveryPlanningWriteError("Plan title is required.");
|
|
19700
|
+
const status = input.status ?? "draft";
|
|
19701
|
+
if (!PLAN_STATES2.includes(status)) throw new DeliveryPlanningWriteError(`Invalid plan status: ${status}.`);
|
|
19702
|
+
const id = nextPlanId(dir);
|
|
19703
|
+
const data = { type: "delivery-plan", id, title: input.title.trim(), status, milestones: [] };
|
|
19704
|
+
if (input.objective?.trim()) data.objective = input.objective.trim();
|
|
19705
|
+
const body = `
|
|
19706
|
+
# ${input.title.trim()}
|
|
19707
|
+
|
|
19708
|
+
_Describe the objective, scope, exclusions, milestones, dependencies, success criteria and risks._
|
|
19709
|
+
`;
|
|
19710
|
+
const filePath = join3(dir, DELIVERY_PLANS_DIR2, `${id}-${slugify3(input.title)}.md`);
|
|
19711
|
+
ensureDir2(join3(dir, DELIVERY_PLANS_DIR2));
|
|
19712
|
+
const content = matter132.stringify(body, data);
|
|
19713
|
+
writeFile2(filePath, content);
|
|
19714
|
+
const plan = parseDeliveryPlan2(dir, filePath, content);
|
|
19715
|
+
if (!plan) throw new DeliveryPlanningWriteError("Failed to parse the Plan just written.");
|
|
19716
|
+
return plan;
|
|
19717
|
+
}
|
|
19718
|
+
function createEpic(dir, input) {
|
|
19719
|
+
if (!input.title?.trim()) throw new DeliveryPlanningWriteError("Epic title is required.");
|
|
19720
|
+
const status = input.status ?? "planned";
|
|
19721
|
+
if (!EPIC_STATES2.includes(status)) throw new DeliveryPlanningWriteError(`Invalid epic status: ${status}.`);
|
|
19722
|
+
if (input.plan) {
|
|
19723
|
+
const plan = getPlan(dir, input.plan);
|
|
19724
|
+
if (!plan) throw new DeliveryPlanningWriteError(`Plan ${input.plan} does not exist.`);
|
|
19725
|
+
if (input.milestone && !plan.milestones.some((m2) => m2.id.toLowerCase() === input.milestone.toLowerCase())) {
|
|
19726
|
+
throw new DeliveryPlanningWriteError(`Milestone ${input.milestone} is not defined in ${input.plan}.`);
|
|
19727
|
+
}
|
|
19728
|
+
} else if (input.milestone) {
|
|
19729
|
+
throw new DeliveryPlanningWriteError("A milestone can only be set together with its plan.");
|
|
19730
|
+
}
|
|
19731
|
+
const id = nextEpicId(dir);
|
|
19732
|
+
const data = { type: "epic", id, title: input.title.trim(), status };
|
|
19733
|
+
if (input.outcome?.trim()) data.outcome = input.outcome.trim();
|
|
19734
|
+
if (input.plan) data.plan = input.plan;
|
|
19735
|
+
if (input.milestone) data.milestone = input.milestone;
|
|
19736
|
+
const body = `
|
|
19737
|
+
# ${input.title.trim()}
|
|
19738
|
+
|
|
19739
|
+
_Describe the expected outcome, success criteria, dependencies and delivery evidence._
|
|
19740
|
+
`;
|
|
19741
|
+
const filePath = join3(dir, EPICS_DIR2, `${id}-${slugify3(input.title)}.md`);
|
|
19742
|
+
ensureDir2(join3(dir, EPICS_DIR2));
|
|
19743
|
+
const content = matter132.stringify(body, data);
|
|
19744
|
+
writeFile2(filePath, content);
|
|
19745
|
+
const epic = parseEpic2(dir, filePath, content);
|
|
19746
|
+
if (!epic) throw new DeliveryPlanningWriteError("Failed to parse the Epic just written.");
|
|
19747
|
+
return epic;
|
|
19748
|
+
}
|
|
19749
|
+
function associateInitiativeWithEpic(dir, initiativeId, epicId) {
|
|
19750
|
+
const ini = getInitiative(dir, initiativeId);
|
|
19751
|
+
if (!ini) throw new DeliveryPlanningWriteError(`Initiative ${initiativeId} not found.`);
|
|
19752
|
+
if (!getEpic(dir, epicId)) throw new DeliveryPlanningWriteError(`Epic ${epicId} does not exist.`);
|
|
19753
|
+
return updateInitiative(dir, ini.id, { epic: epicId });
|
|
19754
|
+
}
|
|
19755
|
+
var USER_FACING_KEYWORDS_EN = [
|
|
19756
|
+
"user",
|
|
19757
|
+
"visitor",
|
|
19758
|
+
"page",
|
|
19759
|
+
"registration",
|
|
19760
|
+
"login",
|
|
19761
|
+
"form",
|
|
19762
|
+
"show",
|
|
19763
|
+
"display",
|
|
19764
|
+
"cta",
|
|
19765
|
+
"flow",
|
|
18947
19766
|
"screen",
|
|
18948
19767
|
"dashboard",
|
|
18949
19768
|
"ui",
|
|
@@ -19107,7 +19926,7 @@ var PROVENANCE = /* @__PURE__ */ new Set(["declared", "derived", "agent-reviewed
|
|
|
19107
19926
|
function isRelative(p) {
|
|
19108
19927
|
return typeof p === "string" && p.trim() !== "" && !/^([a-zA-Z]:[\\/]|\/)/.test(p) && !p.includes("..");
|
|
19109
19928
|
}
|
|
19110
|
-
function
|
|
19929
|
+
function strList32(v) {
|
|
19111
19930
|
return Array.isArray(v) ? v.map((x2) => typeof x2 === "string" ? x2.trim() : "").filter(Boolean) : [];
|
|
19112
19931
|
}
|
|
19113
19932
|
function loadSystemTopology(dir) {
|
|
@@ -19148,7 +19967,7 @@ function parseTopologyContent(raw, dir) {
|
|
|
19148
19967
|
const moduleRaw = typeof e.module === "string" ? e.module.trim() : void 0;
|
|
19149
19968
|
if (moduleRaw && !validModules.has(moduleRaw)) findings.push({ level: "warning", message: `Entity "${id}" references unregistered module "${moduleRaw}".` });
|
|
19150
19969
|
const rawImpl = e.implementation ?? e.implementation_refs;
|
|
19151
|
-
const implementation =
|
|
19970
|
+
const implementation = strList32(rawImpl).filter((p) => {
|
|
19152
19971
|
if (isRelative(p)) return true;
|
|
19153
19972
|
findings.push({ level: "warning", message: `Entity "${id}" implementation ref "${p}" is not a safe relative path and was dropped.` });
|
|
19154
19973
|
return false;
|
|
@@ -19156,7 +19975,7 @@ function parseTopologyContent(raw, dir) {
|
|
|
19156
19975
|
const prov = e.provenance;
|
|
19157
19976
|
const provOrigin = typeof prov === "string" ? prov : prov && typeof prov === "object" && typeof prov.origin === "string" ? String(prov.origin) : void 0;
|
|
19158
19977
|
const provenance = provOrigin && PROVENANCE.has(provOrigin) ? provOrigin : void 0;
|
|
19159
|
-
const evidence =
|
|
19978
|
+
const evidence = strList32(e.evidence ?? (prov && typeof prov === "object" ? prov.evidence_refs : void 0)).filter(isRelative);
|
|
19160
19979
|
seenIds.add(id);
|
|
19161
19980
|
entities.push({
|
|
19162
19981
|
id,
|
|
@@ -19165,7 +19984,7 @@ function parseTopologyContent(raw, dir) {
|
|
|
19165
19984
|
...typeof e.purpose === "string" && e.purpose.trim() ? { purpose: e.purpose.trim() } : {},
|
|
19166
19985
|
...moduleRaw && validModules.has(moduleRaw) ? { moduleId: moduleRaw } : {},
|
|
19167
19986
|
implementationRefs: implementation,
|
|
19168
|
-
knowledgeRefs:
|
|
19987
|
+
knowledgeRefs: strList32(e.knowledge ?? e.knowledge_refs),
|
|
19169
19988
|
...provenance ? { provenance } : {},
|
|
19170
19989
|
evidence
|
|
19171
19990
|
});
|
|
@@ -19199,7 +20018,7 @@ function parseTopologyContent(raw, dir) {
|
|
|
19199
20018
|
}
|
|
19200
20019
|
seenRels.add(key);
|
|
19201
20020
|
const prov = r2.provenance;
|
|
19202
|
-
relationships.push({ from, to, type, evidence:
|
|
20021
|
+
relationships.push({ from, to, type, evidence: strList32(r2.evidence ?? prov?.evidence_refs).filter(isRelative) });
|
|
19203
20022
|
}
|
|
19204
20023
|
return { entities, relationships, findings, ...sourceRevision ? { sourceRevision } : {} };
|
|
19205
20024
|
}
|
|
@@ -19338,7 +20157,7 @@ function parseOriginalSnapshot(fm) {
|
|
|
19338
20157
|
function readBody(filePath) {
|
|
19339
20158
|
try {
|
|
19340
20159
|
const raw = readFile2(filePath);
|
|
19341
|
-
return
|
|
20160
|
+
return matter16(raw).content;
|
|
19342
20161
|
} catch {
|
|
19343
20162
|
return "";
|
|
19344
20163
|
}
|
|
@@ -19422,7 +20241,7 @@ function normalizeImpact(ia) {
|
|
|
19422
20241
|
...v.question ? { question: v.question } : {}
|
|
19423
20242
|
}));
|
|
19424
20243
|
}
|
|
19425
|
-
function
|
|
20244
|
+
function optStr42(v) {
|
|
19426
20245
|
if (typeof v === "string" && v.trim()) return v.trim();
|
|
19427
20246
|
return void 0;
|
|
19428
20247
|
}
|
|
@@ -19437,13 +20256,13 @@ function parseEvidence(fm) {
|
|
|
19437
20256
|
const validations = Array.isArray(d3.validations) ? d3.validations.map((v) => ({
|
|
19438
20257
|
command: String(v.command ?? ""),
|
|
19439
20258
|
status: String(v.status ?? "unknown"),
|
|
19440
|
-
...
|
|
20259
|
+
...optStr42(v.reason) ? { reason: optStr42(v.reason) } : {}
|
|
19441
20260
|
})) : [];
|
|
19442
20261
|
const migrations = Array.isArray(d3.migrations) ? d3.migrations.map((m2) => ({
|
|
19443
20262
|
id: String(m2.id ?? ""),
|
|
19444
20263
|
environment: String(m2.environment ?? ""),
|
|
19445
20264
|
status: String(m2.status ?? "unknown"),
|
|
19446
|
-
...
|
|
20265
|
+
...optStr42(m2.reason) ? { reason: optStr42(m2.reason) } : {}
|
|
19447
20266
|
})) : [];
|
|
19448
20267
|
out.push({
|
|
19449
20268
|
module,
|
|
@@ -19461,8 +20280,8 @@ function parseReleaseGates(fm) {
|
|
|
19461
20280
|
return fm.release_gates.filter((g) => g && typeof g === "object").map((g) => ({
|
|
19462
20281
|
id: String(g.id ?? ""),
|
|
19463
20282
|
status: String(g.status ?? "pending"),
|
|
19464
|
-
...
|
|
19465
|
-
...
|
|
20283
|
+
...optStr42(g.reason) ? { reason: optStr42(g.reason) } : {},
|
|
20284
|
+
...optStr42(g.required_for) ? { requiredFor: optStr42(g.required_for) } : {}
|
|
19466
20285
|
}));
|
|
19467
20286
|
}
|
|
19468
20287
|
function parseExceptions(fm) {
|
|
@@ -19470,9 +20289,9 @@ function parseExceptions(fm) {
|
|
|
19470
20289
|
return fm.completion_exceptions.filter((e) => e && typeof e === "object").map((e) => ({
|
|
19471
20290
|
id: String(e.id ?? ""),
|
|
19472
20291
|
status: String(e.status ?? "proposed"),
|
|
19473
|
-
...
|
|
19474
|
-
...
|
|
19475
|
-
...
|
|
20292
|
+
...optStr42(e.reason) ? { reason: optStr42(e.reason) } : {},
|
|
20293
|
+
...optStr42(e.category) ? { category: optStr42(e.category) } : {},
|
|
20294
|
+
...optStr42(e.impact) ? { impact: optStr42(e.impact) } : {}
|
|
19476
20295
|
}));
|
|
19477
20296
|
}
|
|
19478
20297
|
function parseDecisions(decisions, knowledgeById) {
|
|
@@ -19520,7 +20339,7 @@ function normalizeType(raw) {
|
|
|
19520
20339
|
if (isValidType(t)) return t;
|
|
19521
20340
|
return TYPE_ALIASES[t] ?? null;
|
|
19522
20341
|
}
|
|
19523
|
-
var
|
|
20342
|
+
var WORK_ITEMS_DIR2 = "knowledge/delivery/work-items";
|
|
19524
20343
|
var WorkItemWriteError = class extends Error {
|
|
19525
20344
|
constructor(code, message) {
|
|
19526
20345
|
super(message);
|
|
@@ -19532,11 +20351,11 @@ var WorkItemWriteError = class extends Error {
|
|
|
19532
20351
|
function revisionOf(raw) {
|
|
19533
20352
|
return crypto2.createHash("sha256").update(raw, "utf-8").digest("hex");
|
|
19534
20353
|
}
|
|
19535
|
-
function
|
|
20354
|
+
function slugify4(s) {
|
|
19536
20355
|
return s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 50);
|
|
19537
20356
|
}
|
|
19538
|
-
function
|
|
19539
|
-
const wiDir = join3(dir,
|
|
20357
|
+
function nextWorkItemId2(dir) {
|
|
20358
|
+
const wiDir = join3(dir, WORK_ITEMS_DIR2);
|
|
19540
20359
|
let max = 0;
|
|
19541
20360
|
const walk2 = (d3) => {
|
|
19542
20361
|
if (!exists2(d3)) return;
|
|
@@ -19623,7 +20442,7 @@ function ensureH1(body, title) {
|
|
|
19623
20442
|
${trimmed}`;
|
|
19624
20443
|
}
|
|
19625
20444
|
function serialize2(data, body) {
|
|
19626
|
-
return
|
|
20445
|
+
return matter17.stringify(`
|
|
19627
20446
|
${body.trim()}
|
|
19628
20447
|
`, data);
|
|
19629
20448
|
}
|
|
@@ -19654,7 +20473,7 @@ function createWorkItem(dir, opts) {
|
|
|
19654
20473
|
if (!intent) throw new WorkItemWriteError("INVALID_INPUT", "An intent or summary is required.");
|
|
19655
20474
|
const type = normalizeType(opts.type.trim()) ?? "";
|
|
19656
20475
|
if (!type) throw new WorkItemWriteError("INVALID_INPUT", `Unknown Work Item type "${opts.type}".`);
|
|
19657
|
-
const id =
|
|
20476
|
+
const id = nextWorkItemId2(dir);
|
|
19658
20477
|
const title = intent.split(/\r?\n/)[0].trim().slice(0, 120);
|
|
19659
20478
|
const today22 = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
19660
20479
|
const summary = opts.summary?.trim() || intent;
|
|
@@ -19688,7 +20507,7 @@ function createWorkItem(dir, opts) {
|
|
|
19688
20507
|
relatedKnowledge: [],
|
|
19689
20508
|
scopeConfidence: null
|
|
19690
20509
|
});
|
|
19691
|
-
const relPath = `${
|
|
20510
|
+
const relPath = `${WORK_ITEMS_DIR2}/draft/${id}-${slugify4(title)}.md`;
|
|
19692
20511
|
const filePath = join3(dir, relPath);
|
|
19693
20512
|
if (exists2(filePath)) throw new WorkItemWriteError("INVALID_INPUT", `Work Item file already exists: ${relPath}`);
|
|
19694
20513
|
const raw = serialize2(data, body);
|
|
@@ -20537,7 +21356,7 @@ function detectFormat(raw) {
|
|
|
20537
21356
|
if (!trimmed) throw new ImportError("EMPTY_INPUT", "Import content is empty.");
|
|
20538
21357
|
if (trimmed.startsWith("---")) {
|
|
20539
21358
|
try {
|
|
20540
|
-
const { data } =
|
|
21359
|
+
const { data } = matter18(trimmed);
|
|
20541
21360
|
if (data && typeof data === "object" && Object.keys(data).length > 0) {
|
|
20542
21361
|
const d3 = data;
|
|
20543
21362
|
if (d3.type === "work-item" || d3.id && typeof d3.id === "string" && /^WI-\d+/.test(d3.id)) {
|
|
@@ -20559,7 +21378,7 @@ function parseContent(raw) {
|
|
|
20559
21378
|
let bodyText = trimmed;
|
|
20560
21379
|
if (format === "kaddo-frontmatter" || format === "markdown-frontmatter") {
|
|
20561
21380
|
try {
|
|
20562
|
-
const parsed =
|
|
21381
|
+
const parsed = matter18(trimmed);
|
|
20563
21382
|
frontmatterFields = parsed.data ?? {};
|
|
20564
21383
|
bodyText = parsed.content.trim();
|
|
20565
21384
|
} catch {
|
|
@@ -20656,7 +21475,7 @@ function findDuplicateByHash(dir, hash) {
|
|
|
20656
21475
|
const raw = readFile2(item.filePath);
|
|
20657
21476
|
if (!raw) continue;
|
|
20658
21477
|
try {
|
|
20659
|
-
const { data } =
|
|
21478
|
+
const { data } = matter18(raw);
|
|
20660
21479
|
const source22 = data?.source;
|
|
20661
21480
|
if (source22 && typeof source22 === "object" && !Array.isArray(source22)) {
|
|
20662
21481
|
const s = source22;
|
|
@@ -21363,377 +22182,82 @@ function getConsentState(config) {
|
|
|
21363
22182
|
if (t.consent === "enabled") return "enabled";
|
|
21364
22183
|
if (t.consent === "disabled") return "disabled";
|
|
21365
22184
|
if (t.consent === void 0) {
|
|
21366
|
-
if (t.enabled === true) return "enabled";
|
|
21367
|
-
if (t.enabled === false) return "disabled";
|
|
21368
|
-
}
|
|
21369
|
-
return "unset";
|
|
21370
|
-
}
|
|
21371
|
-
function persistConsent(dir, consent) {
|
|
21372
|
-
const configPath = join3(dir, ".kaddo", "config.yml");
|
|
21373
|
-
if (!exists2(configPath)) return;
|
|
21374
|
-
const raw = readFile2(configPath);
|
|
21375
|
-
const doc = parseYaml11(raw) ?? {};
|
|
21376
|
-
const prev = doc.telemetry ?? {};
|
|
21377
|
-
delete prev.enabled;
|
|
21378
|
-
doc.telemetry = { ...prev, consent, consentVersion: CURRENT_CONSENT_VERSION };
|
|
21379
|
-
writeFile2(configPath, stringifyYaml7(doc));
|
|
21380
|
-
}
|
|
21381
|
-
function isDeferralActive(dir) {
|
|
21382
|
-
const p = join3(dir, ".kaddo", "telemetry-deferred.json");
|
|
21383
|
-
if (!exists2(p)) return false;
|
|
21384
|
-
try {
|
|
21385
|
-
const data = JSON.parse(readFile2(p));
|
|
21386
|
-
return data.deferredUntil > Date.now();
|
|
21387
|
-
} catch {
|
|
21388
|
-
return false;
|
|
21389
|
-
}
|
|
21390
|
-
}
|
|
21391
|
-
function deferConsent(dir) {
|
|
21392
|
-
const p = join3(dir, ".kaddo", "telemetry-deferred.json");
|
|
21393
|
-
writeFile2(p, JSON.stringify({ deferredUntil: Date.now() + DEFERRAL_DURATION_MS }));
|
|
21394
|
-
}
|
|
21395
|
-
function consentNotice() {
|
|
21396
|
-
return '[Telemetry consent] Anonymous usage telemetry has not been configured.\n Collects: command usage, version, lifecycle events.\n Never collects: source code, knowledge, prompts or PII.\n Use kaddo_set_telemetry_consent to enable, disable, or defer ("not-now").';
|
|
21397
|
-
}
|
|
21398
|
-
var TELEMETRY_DIR = ".kaddo/telemetry";
|
|
21399
|
-
var IDENTITY_FILE = "identity.json";
|
|
21400
|
-
function identityPath(dir) {
|
|
21401
|
-
return join3(dir, TELEMETRY_DIR, IDENTITY_FILE);
|
|
21402
|
-
}
|
|
21403
|
-
function loadIdentity(dir) {
|
|
21404
|
-
const p = identityPath(dir);
|
|
21405
|
-
if (!exists2(p)) return null;
|
|
21406
|
-
try {
|
|
21407
|
-
return JSON.parse(readFile2(p));
|
|
21408
|
-
} catch {
|
|
21409
|
-
return null;
|
|
21410
|
-
}
|
|
21411
|
-
}
|
|
21412
|
-
var TELEMETRY_DIR2 = ".kaddo/telemetry";
|
|
21413
|
-
var BUFFER_FILE = "buffer.json";
|
|
21414
|
-
function bufferPath(dir) {
|
|
21415
|
-
return join3(dir, TELEMETRY_DIR2, BUFFER_FILE);
|
|
21416
|
-
}
|
|
21417
|
-
function readBuffer(dir) {
|
|
21418
|
-
const p = bufferPath(dir);
|
|
21419
|
-
if (!exists2(p)) return [];
|
|
21420
|
-
try {
|
|
21421
|
-
const data = JSON.parse(readFile2(p));
|
|
21422
|
-
return Array.isArray(data) ? data : [];
|
|
21423
|
-
} catch {
|
|
21424
|
-
return [];
|
|
21425
|
-
}
|
|
21426
|
-
}
|
|
21427
|
-
function bufferSize(dir) {
|
|
21428
|
-
return readBuffer(dir).length;
|
|
21429
|
-
}
|
|
21430
|
-
function getStatus(dir) {
|
|
21431
|
-
const config = loadConfig2(dir);
|
|
21432
|
-
const consent = getConsentState(config);
|
|
21433
|
-
const enabled = consent === "enabled";
|
|
21434
|
-
const identity = loadIdentity(dir);
|
|
21435
|
-
return {
|
|
21436
|
-
consent,
|
|
21437
|
-
enabled,
|
|
21438
|
-
registered: identity?.registered ?? false,
|
|
21439
|
-
pendingEvents: enabled ? bufferSize(dir) : 0,
|
|
21440
|
-
lastError: null
|
|
21441
|
-
};
|
|
21442
|
-
}
|
|
21443
|
-
var WORK_ITEMS_DIR2 = "knowledge/delivery/work-items";
|
|
21444
|
-
var DRAFT_DIR = `${WORK_ITEMS_DIR2}/draft`;
|
|
21445
|
-
var InitiativeWriteError = class extends Error {
|
|
21446
|
-
constructor(message) {
|
|
21447
|
-
super(message);
|
|
21448
|
-
this.name = "InitiativeWriteError";
|
|
21449
|
-
}
|
|
21450
|
-
};
|
|
21451
|
-
function slugify3(s) {
|
|
21452
|
-
return s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 50);
|
|
21453
|
-
}
|
|
21454
|
-
function today2() {
|
|
21455
|
-
return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
21456
|
-
}
|
|
21457
|
-
function candidateToData(c) {
|
|
21458
|
-
const out = { id: c.id, title: c.title };
|
|
21459
|
-
if (c.type) out.type = c.type;
|
|
21460
|
-
if (c.suggestedKnowledgeLevel) out.suggested_knowledge_level = c.suggestedKnowledgeLevel;
|
|
21461
|
-
if (c.expectedValue) out.expected_value = c.expectedValue;
|
|
21462
|
-
if (c.notes) out.notes = c.notes;
|
|
21463
|
-
if (c.sourceSignals.length) out.source_signals = c.sourceSignals;
|
|
21464
|
-
if (c.dependencies.length) out.dependencies = c.dependencies;
|
|
21465
|
-
if (c.materializedAs) out.materialized_as = c.materializedAs;
|
|
21466
|
-
return out;
|
|
21467
|
-
}
|
|
21468
|
-
function externalLinkToData(l2) {
|
|
21469
|
-
const out = { integration: l2.integration, external_id: l2.externalId };
|
|
21470
|
-
if (l2.externalType) out.external_type = l2.externalType;
|
|
21471
|
-
if (l2.url) out.url = l2.url;
|
|
21472
|
-
if (l2.externalStatus) out.external_status = l2.externalStatus;
|
|
21473
|
-
return out;
|
|
21474
|
-
}
|
|
21475
|
-
function normalizeCandidate(input, fallbackId) {
|
|
21476
|
-
return {
|
|
21477
|
-
id: input.id?.trim() || fallbackId,
|
|
21478
|
-
title: input.title.trim(),
|
|
21479
|
-
type: input.type?.trim() || void 0,
|
|
21480
|
-
suggestedKnowledgeLevel: input.suggestedKnowledgeLevel?.trim() || void 0,
|
|
21481
|
-
expectedValue: input.expectedValue?.trim() || void 0,
|
|
21482
|
-
notes: input.notes?.trim() || void 0,
|
|
21483
|
-
sourceSignals: input.sourceSignals ?? [],
|
|
21484
|
-
dependencies: input.dependencies ?? [],
|
|
21485
|
-
materializedAs: input.materializedAs ?? null
|
|
21486
|
-
};
|
|
21487
|
-
}
|
|
21488
|
-
function defaultBody(title) {
|
|
21489
|
-
return [
|
|
21490
|
-
`# ${title}`,
|
|
21491
|
-
"",
|
|
21492
|
-
"## Goal",
|
|
21493
|
-
"",
|
|
21494
|
-
"_What outcome does this Initiative pursue?_",
|
|
21495
|
-
"",
|
|
21496
|
-
"## Expected Value",
|
|
21497
|
-
"",
|
|
21498
|
-
"_Why it matters._",
|
|
21499
|
-
"",
|
|
21500
|
-
"## Scope",
|
|
21501
|
-
"",
|
|
21502
|
-
"_What this Initiative covers._",
|
|
21503
|
-
"",
|
|
21504
|
-
"## Out of Scope",
|
|
21505
|
-
"",
|
|
21506
|
-
"_What it explicitly does not cover._",
|
|
21507
|
-
"",
|
|
21508
|
-
"## Success Criteria",
|
|
21509
|
-
"",
|
|
21510
|
-
'_Observable outcomes that define completion (not just "all Work Items done")._',
|
|
21511
|
-
"",
|
|
21512
|
-
"## Dependencies",
|
|
21513
|
-
"",
|
|
21514
|
-
"_Other Initiatives, modules, or external work this depends on._",
|
|
21515
|
-
"",
|
|
21516
|
-
"## Work Item Candidates",
|
|
21517
|
-
"",
|
|
21518
|
-
"_Candidates live in frontmatter `candidates`; summarize them here as they evolve._",
|
|
21519
|
-
"",
|
|
21520
|
-
"## Open Questions",
|
|
21521
|
-
"",
|
|
21522
|
-
"_Unresolved questions for this Initiative._",
|
|
21523
|
-
"",
|
|
21524
|
-
"## Learning",
|
|
21525
|
-
"",
|
|
21526
|
-
"_Captured on completion: what was delivered, what stayed out of scope, outcome reached._",
|
|
21527
|
-
""
|
|
21528
|
-
].join("\n");
|
|
21529
|
-
}
|
|
21530
|
-
function buildInitiativeContent(id, input) {
|
|
21531
|
-
const status = input.status ?? "planned";
|
|
21532
|
-
if (!isValidInitiativeStatus2(status)) {
|
|
21533
|
-
throw new InitiativeWriteError(`Invalid initiative status: ${status}.`);
|
|
21534
|
-
}
|
|
21535
|
-
const candidates = (input.candidates ?? []).map(
|
|
21536
|
-
(c, i) => normalizeCandidate(c, `WI-CANDIDATE-${String(i + 1).padStart(3, "0")}`)
|
|
21537
|
-
);
|
|
21538
|
-
const data = {
|
|
21539
|
-
type: "initiative",
|
|
21540
|
-
id,
|
|
21541
|
-
title: input.title.trim(),
|
|
21542
|
-
status,
|
|
21543
|
-
knowledge_level: input.knowledgeLevel ?? "K2",
|
|
21544
|
-
domains: input.domains ?? [],
|
|
21545
|
-
related_capabilities: input.relatedCapabilities ?? [],
|
|
21546
|
-
created_at: today2(),
|
|
21547
|
-
external_links: (input.externalLinks ?? []).map(externalLinkToData),
|
|
21548
|
-
candidates: candidates.map(candidateToData)
|
|
21549
|
-
};
|
|
21550
|
-
if (input.horizon) data.horizon = input.horizon;
|
|
21551
|
-
if (input.priority) data.priority = input.priority;
|
|
21552
|
-
if (input.source) data.source = input.source;
|
|
21553
|
-
if (input.sourceId) data.source_id = input.sourceId;
|
|
21554
|
-
return matter17.stringify("\n" + (input.body ?? defaultBody(input.title.trim())), data);
|
|
21555
|
-
}
|
|
21556
|
-
function createInitiative(dir, input) {
|
|
21557
|
-
if (!input.title?.trim()) throw new InitiativeWriteError("Initiative title is required.");
|
|
21558
|
-
const id = nextInitiativeId(dir);
|
|
21559
|
-
const fileName = `${id}-${slugify3(input.title)}.md`;
|
|
21560
|
-
const iniDir = join3(dir, INITIATIVES_DIR2);
|
|
21561
|
-
ensureDir2(iniDir);
|
|
21562
|
-
const filePath = join3(iniDir, fileName);
|
|
21563
|
-
const content = buildInitiativeContent(id, input);
|
|
21564
|
-
writeFile2(filePath, content);
|
|
21565
|
-
const ini = parseInitiative2(dir, filePath, content);
|
|
21566
|
-
if (!ini) throw new InitiativeWriteError("Failed to parse the Initiative just written.");
|
|
21567
|
-
return ini;
|
|
21568
|
-
}
|
|
21569
|
-
function transitionInitiative(dir, id, to) {
|
|
21570
|
-
const ini = getInitiative(dir, id);
|
|
21571
|
-
if (!ini) throw new InitiativeWriteError(`Initiative ${id} not found.`);
|
|
21572
|
-
if (!isValidInitiativeStatus2(to)) throw new InitiativeWriteError(`Invalid status: ${to}.`);
|
|
21573
|
-
if (ini.status === to) return ini;
|
|
21574
|
-
if (!isValidInitiativeTransition(ini.status, to)) {
|
|
21575
|
-
throw new InitiativeWriteError(`Invalid transition ${ini.status} \u2192 ${to}.`);
|
|
21576
|
-
}
|
|
21577
|
-
const raw = readFile2(ini.filePath);
|
|
21578
|
-
const { data, content } = matter17(raw, {});
|
|
21579
|
-
data.status = to;
|
|
21580
|
-
if (to === "completed") data.completed_at = today2();
|
|
21581
|
-
const newContent = matter17.stringify(content, data);
|
|
21582
|
-
writeFile2(ini.filePath, newContent);
|
|
21583
|
-
const updated = parseInitiative2(dir, ini.filePath, newContent);
|
|
21584
|
-
if (!updated) throw new InitiativeWriteError("Failed to parse the Initiative after transition.");
|
|
21585
|
-
return updated;
|
|
21586
|
-
}
|
|
21587
|
-
function addCandidate(dir, id, candidate) {
|
|
21588
|
-
const ini = getInitiative(dir, id);
|
|
21589
|
-
if (!ini) throw new InitiativeWriteError(`Initiative ${id} not found.`);
|
|
21590
|
-
const raw = readFile2(ini.filePath);
|
|
21591
|
-
const { data, content } = matter17(raw, {});
|
|
21592
|
-
const existing = Array.isArray(data.candidates) ? data.candidates : [];
|
|
21593
|
-
const fallbackId = `WI-CANDIDATE-${String(existing.length + 1).padStart(3, "0")}`;
|
|
21594
|
-
const normalized = normalizeCandidate(candidate, fallbackId);
|
|
21595
|
-
if (ini.candidates.some((c) => c.id === normalized.id)) {
|
|
21596
|
-
throw new InitiativeWriteError(`Candidate ${normalized.id} already exists in ${id}.`);
|
|
22185
|
+
if (t.enabled === true) return "enabled";
|
|
22186
|
+
if (t.enabled === false) return "disabled";
|
|
21597
22187
|
}
|
|
21598
|
-
|
|
21599
|
-
const newContent = matter17.stringify(content, data);
|
|
21600
|
-
writeFile2(ini.filePath, newContent);
|
|
21601
|
-
const updated = parseInitiative2(dir, ini.filePath, newContent);
|
|
21602
|
-
if (!updated) throw new InitiativeWriteError("Failed to parse the Initiative after adding a candidate.");
|
|
21603
|
-
return updated;
|
|
22188
|
+
return "unset";
|
|
21604
22189
|
}
|
|
21605
|
-
function
|
|
21606
|
-
const
|
|
21607
|
-
if (!
|
|
21608
|
-
const raw = readFile2(
|
|
21609
|
-
const
|
|
21610
|
-
|
|
21611
|
-
|
|
21612
|
-
|
|
21613
|
-
|
|
21614
|
-
if (patch.horizon !== void 0) data.horizon = patch.horizon;
|
|
21615
|
-
if (patch.priority !== void 0) data.priority = patch.priority;
|
|
21616
|
-
if (patch.knowledgeLevel !== void 0) data.knowledge_level = patch.knowledgeLevel;
|
|
21617
|
-
if (patch.domains !== void 0) data.domains = patch.domains;
|
|
21618
|
-
if (patch.relatedCapabilities !== void 0) data.related_capabilities = patch.relatedCapabilities;
|
|
21619
|
-
const newContent = matter17.stringify(content, data);
|
|
21620
|
-
writeFile2(ini.filePath, newContent);
|
|
21621
|
-
const updated = parseInitiative2(dir, ini.filePath, newContent);
|
|
21622
|
-
if (!updated) throw new InitiativeWriteError("Failed to parse the Initiative after update.");
|
|
21623
|
-
return updated;
|
|
22190
|
+
function persistConsent(dir, consent) {
|
|
22191
|
+
const configPath = join3(dir, ".kaddo", "config.yml");
|
|
22192
|
+
if (!exists2(configPath)) return;
|
|
22193
|
+
const raw = readFile2(configPath);
|
|
22194
|
+
const doc = parseYaml11(raw) ?? {};
|
|
22195
|
+
const prev = doc.telemetry ?? {};
|
|
22196
|
+
delete prev.enabled;
|
|
22197
|
+
doc.telemetry = { ...prev, consent, consentVersion: CURRENT_CONSENT_VERSION };
|
|
22198
|
+
writeFile2(configPath, stringifyYaml7(doc));
|
|
21624
22199
|
}
|
|
21625
|
-
function
|
|
21626
|
-
const
|
|
21627
|
-
if (!
|
|
21628
|
-
|
|
21629
|
-
|
|
21630
|
-
|
|
21631
|
-
|
|
21632
|
-
|
|
21633
|
-
)) {
|
|
21634
|
-
throw new InitiativeWriteError(`External link ${link.integration}:${link.externalId} already exists on ${id}.`);
|
|
22200
|
+
function isDeferralActive(dir) {
|
|
22201
|
+
const p = join3(dir, ".kaddo", "telemetry-deferred.json");
|
|
22202
|
+
if (!exists2(p)) return false;
|
|
22203
|
+
try {
|
|
22204
|
+
const data = JSON.parse(readFile2(p));
|
|
22205
|
+
return data.deferredUntil > Date.now();
|
|
22206
|
+
} catch {
|
|
22207
|
+
return false;
|
|
21635
22208
|
}
|
|
21636
|
-
const raw = readFile2(ini.filePath);
|
|
21637
|
-
const { data, content } = matter17(raw, {});
|
|
21638
|
-
const existing = Array.isArray(data.external_links) ? data.external_links : [];
|
|
21639
|
-
data.external_links = [...existing, externalLinkToData(link)];
|
|
21640
|
-
const newContent = matter17.stringify(content, data);
|
|
21641
|
-
writeFile2(ini.filePath, newContent);
|
|
21642
|
-
const updated = parseInitiative2(dir, ini.filePath, newContent);
|
|
21643
|
-
if (!updated) throw new InitiativeWriteError("Failed to parse the Initiative after adding an external link.");
|
|
21644
|
-
return updated;
|
|
21645
22209
|
}
|
|
21646
|
-
function
|
|
21647
|
-
const
|
|
21648
|
-
|
|
21649
|
-
const walk2 = (d3) => {
|
|
21650
|
-
if (!exists2(d3)) return;
|
|
21651
|
-
for (const entry of readDir2(d3)) {
|
|
21652
|
-
const full = join3(d3, entry);
|
|
21653
|
-
if (isFile2(full)) {
|
|
21654
|
-
const m2 = entry.match(/WI-(\d+)/);
|
|
21655
|
-
if (m2) max = Math.max(max, parseInt(m2[1], 10));
|
|
21656
|
-
} else if (!entry.startsWith(".")) {
|
|
21657
|
-
walk2(full);
|
|
21658
|
-
}
|
|
21659
|
-
}
|
|
21660
|
-
};
|
|
21661
|
-
walk2(wiDir);
|
|
21662
|
-
return `WI-${String(max + 1).padStart(3, "0")}`;
|
|
22210
|
+
function deferConsent(dir) {
|
|
22211
|
+
const p = join3(dir, ".kaddo", "telemetry-deferred.json");
|
|
22212
|
+
writeFile2(p, JSON.stringify({ deferredUntil: Date.now() + DEFERRAL_DURATION_MS }));
|
|
21663
22213
|
}
|
|
21664
|
-
function
|
|
21665
|
-
|
|
21666
|
-
type: candidate.type ?? "feature",
|
|
21667
|
-
id: wiId,
|
|
21668
|
-
title: candidate.title,
|
|
21669
|
-
knowledge_level: candidate.suggestedKnowledgeLevel ?? "K2",
|
|
21670
|
-
status: "draft",
|
|
21671
|
-
phase: "now",
|
|
21672
|
-
initiative: iniId,
|
|
21673
|
-
domains: [],
|
|
21674
|
-
code: [],
|
|
21675
|
-
created_at: today2(),
|
|
21676
|
-
source: "initiative",
|
|
21677
|
-
source_id: candidate.id,
|
|
21678
|
-
source_initiative: iniId,
|
|
21679
|
-
affected_modules: []
|
|
21680
|
-
};
|
|
21681
|
-
if (candidate.sourceSignals.length) data.source_signals = candidate.sourceSignals;
|
|
21682
|
-
if (candidate.dependencies.length) data.dependencies = candidate.dependencies;
|
|
21683
|
-
const body = [
|
|
21684
|
-
`# ${candidate.title}`,
|
|
21685
|
-
"",
|
|
21686
|
-
`> Materialized from Initiative ${iniId}, candidate ${candidate.id}.`,
|
|
21687
|
-
"",
|
|
21688
|
-
"## Problem",
|
|
21689
|
-
"",
|
|
21690
|
-
candidate.expectedValue ? `_Expected value:_ ${candidate.expectedValue}` : "_Describe the problem._",
|
|
21691
|
-
"",
|
|
21692
|
-
"## Acceptance Criteria",
|
|
21693
|
-
"",
|
|
21694
|
-
"- [ ] _Define at least one testable criterion._",
|
|
21695
|
-
"",
|
|
21696
|
-
"## Out of scope",
|
|
21697
|
-
"",
|
|
21698
|
-
"_Not included in this Work Item._",
|
|
21699
|
-
"",
|
|
21700
|
-
"## Validation",
|
|
21701
|
-
"",
|
|
21702
|
-
"_How will this be validated?_",
|
|
21703
|
-
"",
|
|
21704
|
-
"## Learning",
|
|
21705
|
-
"",
|
|
21706
|
-
"_What did we learn? Update after completion._",
|
|
21707
|
-
""
|
|
21708
|
-
].join("\n");
|
|
21709
|
-
return matter17.stringify("\n" + body, data);
|
|
22214
|
+
function consentNotice() {
|
|
22215
|
+
return '[Telemetry consent] Anonymous usage telemetry has not been configured.\n Collects: command usage, version, lifecycle events.\n Never collects: source code, knowledge, prompts or PII.\n Use kaddo_set_telemetry_consent to enable, disable, or defer ("not-now").';
|
|
21710
22216
|
}
|
|
21711
|
-
|
|
21712
|
-
|
|
21713
|
-
|
|
21714
|
-
|
|
21715
|
-
|
|
21716
|
-
|
|
21717
|
-
|
|
21718
|
-
|
|
21719
|
-
|
|
22217
|
+
var TELEMETRY_DIR = ".kaddo/telemetry";
|
|
22218
|
+
var IDENTITY_FILE = "identity.json";
|
|
22219
|
+
function identityPath(dir) {
|
|
22220
|
+
return join3(dir, TELEMETRY_DIR, IDENTITY_FILE);
|
|
22221
|
+
}
|
|
22222
|
+
function loadIdentity(dir) {
|
|
22223
|
+
const p = identityPath(dir);
|
|
22224
|
+
if (!exists2(p)) return null;
|
|
22225
|
+
try {
|
|
22226
|
+
return JSON.parse(readFile2(p));
|
|
22227
|
+
} catch {
|
|
22228
|
+
return null;
|
|
21720
22229
|
}
|
|
21721
|
-
|
|
21722
|
-
|
|
21723
|
-
|
|
21724
|
-
|
|
21725
|
-
|
|
21726
|
-
|
|
21727
|
-
|
|
21728
|
-
|
|
21729
|
-
|
|
22230
|
+
}
|
|
22231
|
+
var TELEMETRY_DIR2 = ".kaddo/telemetry";
|
|
22232
|
+
var BUFFER_FILE = "buffer.json";
|
|
22233
|
+
function bufferPath(dir) {
|
|
22234
|
+
return join3(dir, TELEMETRY_DIR2, BUFFER_FILE);
|
|
22235
|
+
}
|
|
22236
|
+
function readBuffer(dir) {
|
|
22237
|
+
const p = bufferPath(dir);
|
|
22238
|
+
if (!exists2(p)) return [];
|
|
22239
|
+
try {
|
|
22240
|
+
const data = JSON.parse(readFile2(p));
|
|
22241
|
+
return Array.isArray(data) ? data : [];
|
|
22242
|
+
} catch {
|
|
22243
|
+
return [];
|
|
21730
22244
|
}
|
|
21731
|
-
|
|
21732
|
-
|
|
21733
|
-
|
|
21734
|
-
|
|
21735
|
-
|
|
21736
|
-
|
|
22245
|
+
}
|
|
22246
|
+
function bufferSize(dir) {
|
|
22247
|
+
return readBuffer(dir).length;
|
|
22248
|
+
}
|
|
22249
|
+
function getStatus(dir) {
|
|
22250
|
+
const config = loadConfig2(dir);
|
|
22251
|
+
const consent = getConsentState(config);
|
|
22252
|
+
const enabled = consent === "enabled";
|
|
22253
|
+
const identity = loadIdentity(dir);
|
|
22254
|
+
return {
|
|
22255
|
+
consent,
|
|
22256
|
+
enabled,
|
|
22257
|
+
registered: identity?.registered ?? false,
|
|
22258
|
+
pendingEvents: enabled ? bufferSize(dir) : 0,
|
|
22259
|
+
lastError: null
|
|
22260
|
+
};
|
|
21737
22261
|
}
|
|
21738
22262
|
var SUCCESS_HEADING_RE = /^#{1,6}\s*(success criteria|criterios de[ét]xito|criterios de éxito)\s*$/i;
|
|
21739
22263
|
var HEADING_RE2 = /^#{1,6}\s+/;
|
|
@@ -21843,7 +22367,7 @@ function evaluateInitiativeCompletion(dir, ini) {
|
|
|
21843
22367
|
// ../cli/src/core/resource-write.ts
|
|
21844
22368
|
import fs6 from "fs";
|
|
21845
22369
|
import path5 from "path";
|
|
21846
|
-
import
|
|
22370
|
+
import matter24 from "gray-matter";
|
|
21847
22371
|
var RESOURCES_DIR = "knowledge/tech/resources";
|
|
21848
22372
|
var ResourceWriteError = class extends Error {
|
|
21849
22373
|
constructor(code, message) {
|
|
@@ -21953,7 +22477,7 @@ function buildFrontmatter(id, input, base = {}) {
|
|
|
21953
22477
|
return data;
|
|
21954
22478
|
}
|
|
21955
22479
|
function serialize(data, body) {
|
|
21956
|
-
return
|
|
22480
|
+
return matter24.stringify(`
|
|
21957
22481
|
${body.trim()}
|
|
21958
22482
|
`, data);
|
|
21959
22483
|
}
|
|
@@ -21993,7 +22517,7 @@ function updateResource(dir, id, input) {
|
|
|
21993
22517
|
throw new ResourceWriteError("VALIDATION_FAILED", findings.filter((f3) => f3.level === "error").map((f3) => f3.message).join("; "));
|
|
21994
22518
|
}
|
|
21995
22519
|
const raw = readFile(match.filePath);
|
|
21996
|
-
const parsed =
|
|
22520
|
+
const parsed = matter24(raw);
|
|
21997
22521
|
const base = parsed.data ?? {};
|
|
21998
22522
|
const data = buildFrontmatter(match.id || id, input, base);
|
|
21999
22523
|
let body = parsed.content.trim();
|
|
@@ -22226,7 +22750,7 @@ var CANDIDATE_DISCOVERY_GUIDANCE2 = [
|
|
|
22226
22750
|
// ../cli/src/core/deprecation-write.ts
|
|
22227
22751
|
import fs7 from "fs";
|
|
22228
22752
|
import path6 from "path";
|
|
22229
|
-
import
|
|
22753
|
+
import matter25 from "gray-matter";
|
|
22230
22754
|
var DeprecationWriteError = class extends Error {
|
|
22231
22755
|
constructor(code, message) {
|
|
22232
22756
|
super(message);
|
|
@@ -22244,14 +22768,14 @@ function atomicWrite2(filePath, content) {
|
|
|
22244
22768
|
fs7.renameSync(tmp, filePath);
|
|
22245
22769
|
}
|
|
22246
22770
|
function serialize3(data, body) {
|
|
22247
|
-
return
|
|
22771
|
+
return matter25.stringify(`
|
|
22248
22772
|
${body.trim()}
|
|
22249
22773
|
`, data);
|
|
22250
22774
|
}
|
|
22251
22775
|
function allPlanArtifacts(dir) {
|
|
22252
22776
|
return discoverKnowledge(dir).filter((a2) => a2.type === DEPRECATION_ARTIFACT_TYPE);
|
|
22253
22777
|
}
|
|
22254
|
-
function
|
|
22778
|
+
function nextPlanId2(dir, explicit) {
|
|
22255
22779
|
if (explicit && explicit.trim()) return explicit.trim();
|
|
22256
22780
|
let max = 0;
|
|
22257
22781
|
for (const a2 of allPlanArtifacts(dir)) {
|
|
@@ -22310,7 +22834,7 @@ function validateTargetInput(dir, raw) {
|
|
|
22310
22834
|
function loadPlanRaw(dir, planId) {
|
|
22311
22835
|
const match = allPlanArtifacts(dir).find((a2) => a2.id === planId || a2.title === planId);
|
|
22312
22836
|
if (!match) throw new DeprecationWriteError("NOT_FOUND", `Deprecation Plan "${planId}" not found.`);
|
|
22313
|
-
const parsed =
|
|
22837
|
+
const parsed = matter25(readFile(match.filePath));
|
|
22314
22838
|
const data = { ...parsed.data ?? {} };
|
|
22315
22839
|
return { filePath: match.filePath, data, body: parsed.content.trim() };
|
|
22316
22840
|
}
|
|
@@ -22327,7 +22851,7 @@ function createPlan(dir, input) {
|
|
|
22327
22851
|
if (existing.some((a2) => (a2.title || "").toLowerCase() === title.toLowerCase())) {
|
|
22328
22852
|
throw new DeprecationWriteError("DUPLICATE", `A plan titled "${title}" already exists.`);
|
|
22329
22853
|
}
|
|
22330
|
-
const id =
|
|
22854
|
+
const id = nextPlanId2(dir, input.id);
|
|
22331
22855
|
const data = {
|
|
22332
22856
|
type: DEPRECATION_ARTIFACT_TYPE,
|
|
22333
22857
|
id,
|
|
@@ -22498,12 +23022,12 @@ function markWorkItemReady(root, id, confirm2) {
|
|
|
22498
23022
|
}
|
|
22499
23023
|
const raw = readText(root, item.path);
|
|
22500
23024
|
if (!raw) return fail(`Could not read Work Item file: ${item.path}`);
|
|
22501
|
-
const parsed =
|
|
23025
|
+
const parsed = matter26(raw);
|
|
22502
23026
|
const data = { ...parsed.data };
|
|
22503
23027
|
const today3 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
22504
23028
|
data.status = "ready";
|
|
22505
23029
|
data.ready_at = today3;
|
|
22506
|
-
const newContent =
|
|
23030
|
+
const newContent = matter26.stringify(parsed.content, data);
|
|
22507
23031
|
const posixPath = item.path.replace(/\\/g, "/");
|
|
22508
23032
|
let newRelPath = item.path;
|
|
22509
23033
|
if (posixPath.includes("/work-items/draft/")) {
|
|
@@ -22798,7 +23322,7 @@ function removeDeprecationTargetTool(root, planId, targetId, confirm2) {
|
|
|
22798
23322
|
// src/multirepo.ts
|
|
22799
23323
|
import fs8 from "fs";
|
|
22800
23324
|
import path7 from "path";
|
|
22801
|
-
import
|
|
23325
|
+
import matter27 from "gray-matter";
|
|
22802
23326
|
import { parse as parseYaml12, stringify as stringifyYaml8 } from "yaml";
|
|
22803
23327
|
var ok2 = (data) => ({ ok: true, data });
|
|
22804
23328
|
var fail2 = (message) => ({ ok: false, message });
|
|
@@ -22870,7 +23394,7 @@ function firstParagraph3(md) {
|
|
|
22870
23394
|
}
|
|
22871
23395
|
return "";
|
|
22872
23396
|
}
|
|
22873
|
-
function
|
|
23397
|
+
function slugify5(s) {
|
|
22874
23398
|
return s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
|
|
22875
23399
|
}
|
|
22876
23400
|
function modulesListTool(root, args) {
|
|
@@ -22950,7 +23474,7 @@ function validateWorkItemModulesTool(root, args) {
|
|
|
22950
23474
|
if (!wi) return fail2(`Work Item "${args.workItemId}" not found.`);
|
|
22951
23475
|
const raw = readText(root, wi.path);
|
|
22952
23476
|
if (!raw) return fail2(`Could not read Work Item file.`);
|
|
22953
|
-
const parsed =
|
|
23477
|
+
const parsed = matter27(raw);
|
|
22954
23478
|
const data = parsed.data;
|
|
22955
23479
|
const affectedModules = Array.isArray(data.affected_modules) ? data.affected_modules.map(String) : [];
|
|
22956
23480
|
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) : [] })) : [];
|
|
@@ -23003,7 +23527,7 @@ function getWorkItemContextTool(root, args) {
|
|
|
23003
23527
|
if (!wi) return fail2(`Work Item "${args.workItemId}" not found.`);
|
|
23004
23528
|
const raw = readText(root, wi.path);
|
|
23005
23529
|
if (!raw) return fail2(`Could not read Work Item file.`);
|
|
23006
|
-
const parsed =
|
|
23530
|
+
const parsed = matter27(raw);
|
|
23007
23531
|
const data = parsed.data;
|
|
23008
23532
|
const affectedModules = Array.isArray(data.affected_modules) ? data.affected_modules.map(String) : [];
|
|
23009
23533
|
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) : [] })) : [];
|
|
@@ -23057,7 +23581,7 @@ function extractSection(content, heading) {
|
|
|
23057
23581
|
return out;
|
|
23058
23582
|
}
|
|
23059
23583
|
function buildBranchStrategy(wi, affectedModules, codeEntries, descriptor) {
|
|
23060
|
-
const slug7 =
|
|
23584
|
+
const slug7 = slugify5(wi.title);
|
|
23061
23585
|
const wiLower = wi.id.toLowerCase();
|
|
23062
23586
|
const coreBranch = `feature/${wiLower}-${slug7}`;
|
|
23063
23587
|
const moduleBranches = {};
|
|
@@ -23089,7 +23613,7 @@ function suggestBranchStrategyTool(root, args) {
|
|
|
23089
23613
|
if (!wi) return fail2(`Work Item "${args.workItemId}" not found.`);
|
|
23090
23614
|
const raw = readText(root, wi.path);
|
|
23091
23615
|
if (!raw) return fail2(`Could not read Work Item file.`);
|
|
23092
|
-
const parsed =
|
|
23616
|
+
const parsed = matter27(raw);
|
|
23093
23617
|
const data = parsed.data;
|
|
23094
23618
|
const affectedModules = Array.isArray(data.affected_modules) ? data.affected_modules.map(String) : [];
|
|
23095
23619
|
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) : [] })) : [];
|
|
@@ -23120,7 +23644,7 @@ function exportCapsuleTool(root, args) {
|
|
|
23120
23644
|
if (!mc) warnings.push("module-context.md not found for this module.");
|
|
23121
23645
|
if (mc && isPlaceholder2(mc)) warnings.push("module-context.md contains placeholder sections.");
|
|
23122
23646
|
capsuleType = "module";
|
|
23123
|
-
capsuleName = `${
|
|
23647
|
+
capsuleName = `${slugify5(config.project?.name ?? "project")}-${args.module}`;
|
|
23124
23648
|
capsuleData = {
|
|
23125
23649
|
type: "knowledge-capsule",
|
|
23126
23650
|
system: `${config.project?.name ?? "unknown"}/${args.module}`,
|
|
@@ -23142,7 +23666,7 @@ function exportCapsuleTool(root, args) {
|
|
|
23142
23666
|
if (mc && isPlaceholder2(mc)) warnings.push("module-context.md contains placeholder sections.");
|
|
23143
23667
|
const modId = config.module?.id ?? args.module;
|
|
23144
23668
|
capsuleType = "module";
|
|
23145
|
-
capsuleName =
|
|
23669
|
+
capsuleName = slugify5(modId);
|
|
23146
23670
|
capsuleData = {
|
|
23147
23671
|
type: "knowledge-capsule",
|
|
23148
23672
|
system: `${config.multirepo?.parent_system ?? "unknown"}/${modId}`,
|
|
@@ -23170,7 +23694,7 @@ function exportCapsuleTool(root, args) {
|
|
|
23170
23694
|
responsibilities.push(mc ? `[${m2.id}] ${firstParagraph3(mc) || m2.name || m2.id}` : `[${m2.id}] ${m2.name || m2.id} (no module-context)`);
|
|
23171
23695
|
}
|
|
23172
23696
|
capsuleType = "system";
|
|
23173
|
-
capsuleName = `${
|
|
23697
|
+
capsuleName = `${slugify5(config.project?.name ?? "project")}-system`;
|
|
23174
23698
|
capsuleData = {
|
|
23175
23699
|
type: "knowledge-capsule",
|
|
23176
23700
|
system: config.project?.name ?? "unknown",
|
|
@@ -23191,7 +23715,7 @@ function exportCapsuleTool(root, args) {
|
|
|
23191
23715
|
if (mc && isPlaceholder2(mc)) warnings.push("module-context.md contains placeholder sections.");
|
|
23192
23716
|
const modId = config.module?.id ?? "module";
|
|
23193
23717
|
capsuleType = "module";
|
|
23194
|
-
capsuleName =
|
|
23718
|
+
capsuleName = slugify5(modId);
|
|
23195
23719
|
capsuleData = {
|
|
23196
23720
|
type: "knowledge-capsule",
|
|
23197
23721
|
system: `${config.multirepo?.parent_system ?? "unknown"}/${modId}`,
|
|
@@ -23207,7 +23731,7 @@ function exportCapsuleTool(root, args) {
|
|
|
23207
23731
|
};
|
|
23208
23732
|
} else {
|
|
23209
23733
|
capsuleType = "project";
|
|
23210
|
-
capsuleName =
|
|
23734
|
+
capsuleName = slugify5(config.project?.name ?? "project");
|
|
23211
23735
|
capsuleData = {
|
|
23212
23736
|
type: "knowledge-capsule",
|
|
23213
23737
|
system: config.project?.name ?? "unknown",
|
|
@@ -23370,7 +23894,7 @@ var PROVENANCE2 = /* @__PURE__ */ new Set(["declared", "derived", "agent-reviewe
|
|
|
23370
23894
|
function isRelative2(p) {
|
|
23371
23895
|
return typeof p === "string" && p.trim() !== "" && !/^([a-zA-Z]:[\\/]|\/)/.test(p) && !p.includes("..");
|
|
23372
23896
|
}
|
|
23373
|
-
function
|
|
23897
|
+
function strList4(v) {
|
|
23374
23898
|
return Array.isArray(v) ? v.map((x2) => typeof x2 === "string" ? x2.trim() : "").filter(Boolean) : [];
|
|
23375
23899
|
}
|
|
23376
23900
|
function loadSystemTopology2(dir) {
|
|
@@ -23411,7 +23935,7 @@ function parseTopologyContent2(raw, dir) {
|
|
|
23411
23935
|
const moduleRaw = typeof e.module === "string" ? e.module.trim() : void 0;
|
|
23412
23936
|
if (moduleRaw && !validModules.has(moduleRaw)) findings.push({ level: "warning", message: `Entity "${id}" references unregistered module "${moduleRaw}".` });
|
|
23413
23937
|
const rawImpl = e.implementation ?? e.implementation_refs;
|
|
23414
|
-
const implementation =
|
|
23938
|
+
const implementation = strList4(rawImpl).filter((p) => {
|
|
23415
23939
|
if (isRelative2(p)) return true;
|
|
23416
23940
|
findings.push({ level: "warning", message: `Entity "${id}" implementation ref "${p}" is not a safe relative path and was dropped.` });
|
|
23417
23941
|
return false;
|
|
@@ -23419,7 +23943,7 @@ function parseTopologyContent2(raw, dir) {
|
|
|
23419
23943
|
const prov = e.provenance;
|
|
23420
23944
|
const provOrigin = typeof prov === "string" ? prov : prov && typeof prov === "object" && typeof prov.origin === "string" ? String(prov.origin) : void 0;
|
|
23421
23945
|
const provenance = provOrigin && PROVENANCE2.has(provOrigin) ? provOrigin : void 0;
|
|
23422
|
-
const evidence =
|
|
23946
|
+
const evidence = strList4(e.evidence ?? (prov && typeof prov === "object" ? prov.evidence_refs : void 0)).filter(isRelative2);
|
|
23423
23947
|
seenIds.add(id);
|
|
23424
23948
|
entities.push({
|
|
23425
23949
|
id,
|
|
@@ -23428,7 +23952,7 @@ function parseTopologyContent2(raw, dir) {
|
|
|
23428
23952
|
...typeof e.purpose === "string" && e.purpose.trim() ? { purpose: e.purpose.trim() } : {},
|
|
23429
23953
|
...moduleRaw && validModules.has(moduleRaw) ? { moduleId: moduleRaw } : {},
|
|
23430
23954
|
implementationRefs: implementation,
|
|
23431
|
-
knowledgeRefs:
|
|
23955
|
+
knowledgeRefs: strList4(e.knowledge ?? e.knowledge_refs),
|
|
23432
23956
|
...provenance ? { provenance } : {},
|
|
23433
23957
|
evidence
|
|
23434
23958
|
});
|
|
@@ -23462,7 +23986,7 @@ function parseTopologyContent2(raw, dir) {
|
|
|
23462
23986
|
}
|
|
23463
23987
|
seenRels.add(key);
|
|
23464
23988
|
const prov = r2.provenance;
|
|
23465
|
-
relationships.push({ from, to, type, evidence:
|
|
23989
|
+
relationships.push({ from, to, type, evidence: strList4(r2.evidence ?? prov?.evidence_refs).filter(isRelative2) });
|
|
23466
23990
|
}
|
|
23467
23991
|
return { entities, relationships, findings, ...sourceRevision ? { sourceRevision } : {} };
|
|
23468
23992
|
}
|
|
@@ -23849,14 +24373,14 @@ function systemImpactCandidatesTool(root, args) {
|
|
|
23849
24373
|
}
|
|
23850
24374
|
|
|
23851
24375
|
// ../cli/src/services/integrations.ts
|
|
23852
|
-
import
|
|
24376
|
+
import matter29 from "gray-matter";
|
|
23853
24377
|
import { parse as parseYaml14, stringify as stringifyYaml10 } from "yaml";
|
|
23854
24378
|
|
|
23855
24379
|
// ../cli/src/core/work-item-write.ts
|
|
23856
24380
|
import fs10 from "fs";
|
|
23857
24381
|
import path9 from "path";
|
|
23858
24382
|
import crypto4 from "crypto";
|
|
23859
|
-
import
|
|
24383
|
+
import matter28 from "gray-matter";
|
|
23860
24384
|
var SECTION_ORDER2 = [
|
|
23861
24385
|
"Actor",
|
|
23862
24386
|
"Outcome",
|
|
@@ -24904,7 +25428,7 @@ async function integrationsWorkItemTool(root, args) {
|
|
|
24904
25428
|
}
|
|
24905
25429
|
|
|
24906
25430
|
// ../cli/src/core/context-pack.ts
|
|
24907
|
-
import
|
|
25431
|
+
import matter30 from "gray-matter";
|
|
24908
25432
|
var CONTEXT_PACK_VERSION = "1";
|
|
24909
25433
|
var ARCH_DIR4 = "knowledge";
|
|
24910
25434
|
function readScanJson(dir) {
|
|
@@ -24917,7 +25441,7 @@ function readScanJson(dir) {
|
|
|
24917
25441
|
}
|
|
24918
25442
|
}
|
|
24919
25443
|
function firstParagraph4(markdown) {
|
|
24920
|
-
const body =
|
|
25444
|
+
const body = matter30(markdown).content.trim();
|
|
24921
25445
|
const para = body.split("\n\n").map((p) => p.trim()).find((p) => p && !p.startsWith("#"));
|
|
24922
25446
|
return para ?? "";
|
|
24923
25447
|
}
|
|
@@ -25900,7 +26424,7 @@ function renderUnderstand(plan) {
|
|
|
25900
26424
|
|
|
25901
26425
|
// ../cli/src/core/knowledge-consolidation.ts
|
|
25902
26426
|
import { createHash as createHash3 } from "crypto";
|
|
25903
|
-
import
|
|
26427
|
+
import matter31 from "gray-matter";
|
|
25904
26428
|
var KNOWLEDGE7 = "knowledge";
|
|
25905
26429
|
var CONSOLIDATION_CATEGORIES = [
|
|
25906
26430
|
"knowledge_redundancy",
|
|
@@ -25910,12 +26434,12 @@ var CONSOLIDATION_CATEGORIES = [
|
|
|
25910
26434
|
"potentially_outdated",
|
|
25911
26435
|
"knowledge_organization"
|
|
25912
26436
|
];
|
|
25913
|
-
function
|
|
26437
|
+
function toPosix9(p) {
|
|
25914
26438
|
return p.replace(/\\/g, "/");
|
|
25915
26439
|
}
|
|
25916
26440
|
function relFromDir(dir, filePath) {
|
|
25917
|
-
const d3 =
|
|
25918
|
-
const f3 =
|
|
26441
|
+
const d3 = toPosix9(dir).replace(/\/+$/, "");
|
|
26442
|
+
const f3 = toPosix9(filePath);
|
|
25919
26443
|
return f3.startsWith(d3 + "/") ? f3.slice(d3.length + 1) : f3;
|
|
25920
26444
|
}
|
|
25921
26445
|
function sha256(content) {
|
|
@@ -26033,7 +26557,7 @@ function stableOpportunityId(category, sources) {
|
|
|
26033
26557
|
function layerOf(a2) {
|
|
26034
26558
|
const byType = layerForType(a2.type);
|
|
26035
26559
|
if (byType) return byType;
|
|
26036
|
-
const p =
|
|
26560
|
+
const p = toPosix9(a2.filePath);
|
|
26037
26561
|
if (p.includes(`/${KNOWLEDGE7}/business/`)) return "Business";
|
|
26038
26562
|
if (p.includes(`/${KNOWLEDGE7}/product/`)) return "Product";
|
|
26039
26563
|
if (p.includes(`/${KNOWLEDGE7}/tech/`)) return "Tech";
|
|
@@ -26057,7 +26581,7 @@ function buildConsolidationPlan(dir, opts = {}) {
|
|
|
26057
26581
|
const loaded = rawArtifacts.map((a2) => {
|
|
26058
26582
|
let body = "";
|
|
26059
26583
|
try {
|
|
26060
|
-
body =
|
|
26584
|
+
body = matter31(readFile(a2.filePath)).content;
|
|
26061
26585
|
} catch {
|
|
26062
26586
|
body = "";
|
|
26063
26587
|
}
|
|
@@ -26311,14 +26835,14 @@ function describeScope(scope) {
|
|
|
26311
26835
|
return `module:${scope.module}`;
|
|
26312
26836
|
}
|
|
26313
26837
|
function isWorkItem(la) {
|
|
26314
|
-
return
|
|
26838
|
+
return toPosix9(la.artifact.filePath).includes("/delivery/work-items/");
|
|
26315
26839
|
}
|
|
26316
26840
|
function isCompletedWorkItem(la) {
|
|
26317
|
-
const p =
|
|
26841
|
+
const p = toPosix9(la.artifact.filePath);
|
|
26318
26842
|
return p.includes("/delivery/work-items/completed/") || isWorkItem(la) && la.artifact.status === "completed";
|
|
26319
26843
|
}
|
|
26320
26844
|
function isScaffolding(la) {
|
|
26321
|
-
const p =
|
|
26845
|
+
const p = toPosix9(la.artifact.filePath);
|
|
26322
26846
|
return p.includes(`/${KNOWLEDGE7}/agents/`) || p.includes(`/${KNOWLEDGE7}/skills/`) || p.includes(`/${KNOWLEDGE7}/external/`) || la.artifact.type === "knowledge-capsule";
|
|
26323
26847
|
}
|
|
26324
26848
|
var NON_KNOWLEDGE_DOC_TYPES = /* @__PURE__ */ new Set([
|
|
@@ -26386,7 +26910,7 @@ function decisionFileExists(dir, decisionId) {
|
|
|
26386
26910
|
function hintInScope(hint, scoped) {
|
|
26387
26911
|
if (scoped.length === 0) return false;
|
|
26388
26912
|
return scoped.some(
|
|
26389
|
-
(la) => la.artifact.id === hint.artifact_id || hint.path &&
|
|
26913
|
+
(la) => la.artifact.id === hint.artifact_id || hint.path && toPosix9(la.artifact.filePath).endsWith(toPosix9(hint.path))
|
|
26390
26914
|
);
|
|
26391
26915
|
}
|
|
26392
26916
|
function relationshipOpportunityFromHint(hint) {
|
|
@@ -26770,6 +27294,104 @@ function completeInitiativeTool(root, args) {
|
|
|
26770
27294
|
}
|
|
26771
27295
|
}
|
|
26772
27296
|
|
|
27297
|
+
// src/planning.ts
|
|
27298
|
+
var ok6 = (data) => ({ ok: true, data });
|
|
27299
|
+
var fail5 = (message) => ({ ok: false, message });
|
|
27300
|
+
function previewOrApply2(confirm2, action, effect, apply) {
|
|
27301
|
+
if (!confirm2) return ok6({ action, effect, instruction: "Call again with confirm=true to apply." });
|
|
27302
|
+
try {
|
|
27303
|
+
return ok6({ applied: true, result: apply() });
|
|
27304
|
+
} catch (err) {
|
|
27305
|
+
if (err instanceof DeliveryPlanningWriteError) return fail5(err.message);
|
|
27306
|
+
throw err;
|
|
27307
|
+
}
|
|
27308
|
+
}
|
|
27309
|
+
function listPlansTool(root) {
|
|
27310
|
+
return ok6(discoverDeliveryPlans2(root).map((p) => {
|
|
27311
|
+
const prog = computePlanProgress(root, p);
|
|
27312
|
+
return { id: p.id, title: p.title, status: p.status, milestones: p.milestones.length, epics: prog.epics.total, delivery: prog.delivery };
|
|
27313
|
+
}));
|
|
27314
|
+
}
|
|
27315
|
+
function getPlanTool(root, id) {
|
|
27316
|
+
const plan = getPlan(root, id);
|
|
27317
|
+
if (!plan) return fail5(`Plan ${id} not found.`);
|
|
27318
|
+
return ok6({
|
|
27319
|
+
id: plan.id,
|
|
27320
|
+
title: plan.title,
|
|
27321
|
+
status: plan.status,
|
|
27322
|
+
objective: plan.objective,
|
|
27323
|
+
milestones: plan.milestones,
|
|
27324
|
+
dependsOn: plan.dependsOn,
|
|
27325
|
+
successCriteria: plan.successCriteria,
|
|
27326
|
+
risks: plan.risks,
|
|
27327
|
+
references: plan.references,
|
|
27328
|
+
epics: epicsInPlan(root, plan.id).map((e) => ({ id: e.id, title: e.title, status: e.status, milestone: e.milestone }))
|
|
27329
|
+
});
|
|
27330
|
+
}
|
|
27331
|
+
function getPlanProgressTool(root, id) {
|
|
27332
|
+
const plan = getPlan(root, id);
|
|
27333
|
+
if (!plan) return fail5(`Plan ${id} not found.`);
|
|
27334
|
+
return ok6(computePlanProgress(root, plan));
|
|
27335
|
+
}
|
|
27336
|
+
function listEpicsTool(root) {
|
|
27337
|
+
return ok6(discoverEpics2(root).map((e) => {
|
|
27338
|
+
const prog = computeEpicProgress(root, e);
|
|
27339
|
+
return { id: e.id, title: e.title, status: e.status, plan: e.plan, milestone: e.milestone, delivery: prog.delivery };
|
|
27340
|
+
}));
|
|
27341
|
+
}
|
|
27342
|
+
function getEpicTool(root, id) {
|
|
27343
|
+
const epic = getEpic(root, id);
|
|
27344
|
+
if (!epic) return fail5(`Epic ${id} not found.`);
|
|
27345
|
+
return ok6({
|
|
27346
|
+
id: epic.id,
|
|
27347
|
+
title: epic.title,
|
|
27348
|
+
status: epic.status,
|
|
27349
|
+
outcome: epic.outcome,
|
|
27350
|
+
plan: epic.plan,
|
|
27351
|
+
milestone: epic.milestone,
|
|
27352
|
+
dependsOn: epic.dependsOn,
|
|
27353
|
+
successCriteria: epic.successCriteria,
|
|
27354
|
+
relatedCapabilities: epic.relatedCapabilities,
|
|
27355
|
+
initiatives: initiativesInEpic(root, epic.id).map((i) => ({ id: i.id, title: i.title, status: i.status, owners: i.owners, contributors: i.contributors })),
|
|
27356
|
+
workItems: workItemsInEpic(root, epic.id).map((w3) => ({ id: w3.id, title: w3.title, status: w3.lifecycle }))
|
|
27357
|
+
});
|
|
27358
|
+
}
|
|
27359
|
+
function getEpicProgressTool(root, id) {
|
|
27360
|
+
const epic = getEpic(root, id);
|
|
27361
|
+
if (!epic) return fail5(`Epic ${id} not found.`);
|
|
27362
|
+
return ok6(computeEpicProgress(root, epic));
|
|
27363
|
+
}
|
|
27364
|
+
function validatePlanningTool(root) {
|
|
27365
|
+
return ok6({ findings: validatePlanningGraph(root) });
|
|
27366
|
+
}
|
|
27367
|
+
function createPlanTool(root, args) {
|
|
27368
|
+
if (!args.title?.trim()) return fail5("title is required.");
|
|
27369
|
+
return previewOrApply2(
|
|
27370
|
+
args.confirm,
|
|
27371
|
+
"create-delivery-plan",
|
|
27372
|
+
`A new Delivery Plan "${args.title.trim()}" will be created under knowledge/delivery/plans/.`,
|
|
27373
|
+
() => createDeliveryPlan(root, { title: args.title, objective: args.objective })
|
|
27374
|
+
);
|
|
27375
|
+
}
|
|
27376
|
+
function createEpicTool(root, args) {
|
|
27377
|
+
if (!args.title?.trim()) return fail5("title is required.");
|
|
27378
|
+
return previewOrApply2(
|
|
27379
|
+
args.confirm,
|
|
27380
|
+
"create-epic",
|
|
27381
|
+
`A new Epic "${args.title.trim()}"${args.plan ? ` in ${args.plan}` : ""} will be created under knowledge/delivery/epics/.`,
|
|
27382
|
+
() => createEpic(root, { title: args.title, plan: args.plan, milestone: args.milestone, outcome: args.outcome })
|
|
27383
|
+
);
|
|
27384
|
+
}
|
|
27385
|
+
function associateInitiativeTool(root, args) {
|
|
27386
|
+
if (!args.epic?.trim() || !args.initiative?.trim()) return fail5("epic and initiative are required.");
|
|
27387
|
+
return previewOrApply2(
|
|
27388
|
+
args.confirm,
|
|
27389
|
+
"associate-initiative-epic",
|
|
27390
|
+
`Initiative ${args.initiative} will be associated with Epic ${args.epic}.`,
|
|
27391
|
+
() => associateInitiativeWithEpic(root, args.initiative, args.epic)
|
|
27392
|
+
);
|
|
27393
|
+
}
|
|
27394
|
+
|
|
26773
27395
|
// src/server.ts
|
|
26774
27396
|
var SERVER_NAME = "kaddo";
|
|
26775
27397
|
var require2 = createRequire(import.meta.url);
|
|
@@ -27334,6 +27956,58 @@ function createServer(root) {
|
|
|
27334
27956
|
{ title: "Get initiative progress", description: `Planning coverage (candidates materialized/total) and delivery progress (Work Items by lifecycle state). ${INITIATIVE_READ_NOTE}`, inputSchema: { id: z4.string() } },
|
|
27335
27957
|
async (args) => toolText(guarded(root, () => getInitiativeProgressTool(root, args.id)))
|
|
27336
27958
|
);
|
|
27959
|
+
const PLANNING_READ_NOTE = "Read-only. Plans and Epics are an OPTIONAL planning layer; progress is derived from real Work Item state and never auto-completes.";
|
|
27960
|
+
const PLANNING_WRITE_NOTE = "Without confirm=true returns a preview. With confirm=true applies the change. Writes only under knowledge/delivery/; never changes another element lifecycle, never infers responsibility from Git.";
|
|
27961
|
+
server.registerTool(
|
|
27962
|
+
"kaddo_list_plans",
|
|
27963
|
+
{ title: "List delivery plans", description: `List delivery plans with milestone count, epics and delivery progress. ${PLANNING_READ_NOTE}`, inputSchema: {} },
|
|
27964
|
+
async () => toolText(guarded(root, () => listPlansTool(root)))
|
|
27965
|
+
);
|
|
27966
|
+
server.registerTool(
|
|
27967
|
+
"kaddo_get_plan",
|
|
27968
|
+
{ title: "Get delivery plan", description: `Get one plan: objective, milestones, dependencies, success criteria and its epics. ${PLANNING_READ_NOTE}`, inputSchema: { id: z4.string() } },
|
|
27969
|
+
async (args) => toolText(guarded(root, () => getPlanTool(root, args.id)))
|
|
27970
|
+
);
|
|
27971
|
+
server.registerTool(
|
|
27972
|
+
"kaddo_get_plan_progress",
|
|
27973
|
+
{ title: "Get plan progress", description: `Planning coverage, delivery progress and completion readiness for a plan. ${PLANNING_READ_NOTE}`, inputSchema: { id: z4.string() } },
|
|
27974
|
+
async (args) => toolText(guarded(root, () => getPlanProgressTool(root, args.id)))
|
|
27975
|
+
);
|
|
27976
|
+
server.registerTool(
|
|
27977
|
+
"kaddo_list_epics",
|
|
27978
|
+
{ title: "List epics", description: `List epics with their plan/milestone and delivery progress. ${PLANNING_READ_NOTE}`, inputSchema: {} },
|
|
27979
|
+
async () => toolText(guarded(root, () => listEpicsTool(root)))
|
|
27980
|
+
);
|
|
27981
|
+
server.registerTool(
|
|
27982
|
+
"kaddo_get_epic",
|
|
27983
|
+
{ title: "Get epic", description: `Get one epic: outcome, plan/milestone, dependencies, initiatives and work items (derived). ${PLANNING_READ_NOTE}`, inputSchema: { id: z4.string() } },
|
|
27984
|
+
async (args) => toolText(guarded(root, () => getEpicTool(root, args.id)))
|
|
27985
|
+
);
|
|
27986
|
+
server.registerTool(
|
|
27987
|
+
"kaddo_get_epic_progress",
|
|
27988
|
+
{ title: "Get epic progress", description: `Planning coverage, delivery progress and completion readiness for an epic. ${PLANNING_READ_NOTE}`, inputSchema: { id: z4.string() } },
|
|
27989
|
+
async (args) => toolText(guarded(root, () => getEpicProgressTool(root, args.id)))
|
|
27990
|
+
);
|
|
27991
|
+
server.registerTool(
|
|
27992
|
+
"kaddo_validate_planning",
|
|
27993
|
+
{ title: "Validate planning graph", description: `Deterministic validation: invalid references, dependency cycles, duplicate milestones and pending dependencies. ${PLANNING_READ_NOTE}`, inputSchema: {} },
|
|
27994
|
+
async () => toolText(guarded(root, () => validatePlanningTool(root)))
|
|
27995
|
+
);
|
|
27996
|
+
server.registerTool(
|
|
27997
|
+
"kaddo_create_plan",
|
|
27998
|
+
{ title: "Create delivery plan", description: `Create a new delivery plan. ${PLANNING_WRITE_NOTE}`, inputSchema: { title: z4.string(), objective: z4.string().optional(), confirm: z4.boolean().optional() } },
|
|
27999
|
+
async (args) => toolText(guarded(root, () => createPlanTool(root, args)))
|
|
28000
|
+
);
|
|
28001
|
+
server.registerTool(
|
|
28002
|
+
"kaddo_create_epic",
|
|
28003
|
+
{ title: "Create epic", description: `Create a new epic, optionally in a plan/milestone (validated). ${PLANNING_WRITE_NOTE}`, inputSchema: { title: z4.string(), plan: z4.string().optional(), milestone: z4.string().optional(), outcome: z4.string().optional(), confirm: z4.boolean().optional() } },
|
|
28004
|
+
async (args) => toolText(guarded(root, () => createEpicTool(root, args)))
|
|
28005
|
+
);
|
|
28006
|
+
server.registerTool(
|
|
28007
|
+
"kaddo_associate_initiative_epic",
|
|
28008
|
+
{ title: "Associate initiative with epic", description: `Associate an existing Initiative with an Epic (validated). ${PLANNING_WRITE_NOTE}`, inputSchema: { epic: z4.string(), initiative: z4.string(), confirm: z4.boolean().optional() } },
|
|
28009
|
+
async (args) => toolText(guarded(root, () => associateInitiativeTool(root, args)))
|
|
28010
|
+
);
|
|
27337
28011
|
const INITIATIVE_WRITE_NOTE = "Without confirm=true returns a preview. With confirm=true applies the change. Writes only under knowledge/delivery/.";
|
|
27338
28012
|
server.registerTool(
|
|
27339
28013
|
"kaddo_create_initiative",
|