@kaddo/cli 3.14.0 → 3.16.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 +707 -73
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -600,6 +600,21 @@ ${issues}`);
|
|
|
600
600
|
}
|
|
601
601
|
return parsed.data;
|
|
602
602
|
}
|
|
603
|
+
function requireConfig(dir) {
|
|
604
|
+
let config;
|
|
605
|
+
try {
|
|
606
|
+
config = loadConfig(dir);
|
|
607
|
+
} catch (err) {
|
|
608
|
+
const message = err instanceof ConfigError ? err.message : String(err);
|
|
609
|
+
console.error(message);
|
|
610
|
+
process.exit(1);
|
|
611
|
+
}
|
|
612
|
+
if (!config) {
|
|
613
|
+
console.error("No .kaddo/config.yml found. Run `kaddo init` first.");
|
|
614
|
+
process.exit(1);
|
|
615
|
+
}
|
|
616
|
+
return config;
|
|
617
|
+
}
|
|
603
618
|
function nextStepsForState(state) {
|
|
604
619
|
switch (state) {
|
|
605
620
|
case "new":
|
|
@@ -624,8 +639,8 @@ function createGuidanceForState(state) {
|
|
|
624
639
|
}
|
|
625
640
|
function describeProject(config) {
|
|
626
641
|
const state = config.project.state;
|
|
627
|
-
const
|
|
628
|
-
return `${
|
|
642
|
+
const stateLabel3 = state === "pre-ai" ? "pre-AI" : state;
|
|
643
|
+
return `${stateLabel3} ${config.project.structure}`;
|
|
629
644
|
}
|
|
630
645
|
|
|
631
646
|
// src/core/command-help.ts
|
|
@@ -1611,6 +1626,33 @@ var RESPONSIBILITY_MATRIX = {
|
|
|
1611
1626
|
],
|
|
1612
1627
|
next: ["kaddo scan", "kaddo owners suggest", "kaddo guard", "kaddo explain"]
|
|
1613
1628
|
},
|
|
1629
|
+
"capsule-agent": {
|
|
1630
|
+
agent: "capsule-agent",
|
|
1631
|
+
responsibleFor: ["Refining/validating a Knowledge Capsule for external sharing"],
|
|
1632
|
+
produces: [".kaddo/exports/<system>.capsule.md"],
|
|
1633
|
+
canSuggest: ["kaddo capsule export"],
|
|
1634
|
+
cannotSuggest: [
|
|
1635
|
+
"exporting secrets",
|
|
1636
|
+
"exporting source code",
|
|
1637
|
+
"inventing contracts",
|
|
1638
|
+
"code",
|
|
1639
|
+
"git"
|
|
1640
|
+
],
|
|
1641
|
+
next: ["kaddo capsule export"]
|
|
1642
|
+
},
|
|
1643
|
+
"ownership-agent": {
|
|
1644
|
+
agent: "ownership-agent",
|
|
1645
|
+
responsibleFor: ["Precise code: ownership for Work Items and artifacts"],
|
|
1646
|
+
produces: ["proposed code: globs"],
|
|
1647
|
+
canSuggest: ["kaddo owners suggest", "kaddo guard"],
|
|
1648
|
+
cannotSuggest: [
|
|
1649
|
+
"code",
|
|
1650
|
+
"branches",
|
|
1651
|
+
"commits",
|
|
1652
|
+
"modifying files without confirmation"
|
|
1653
|
+
],
|
|
1654
|
+
next: ["kaddo owners suggest", "kaddo guard"]
|
|
1655
|
+
},
|
|
1614
1656
|
"guard-agent": {
|
|
1615
1657
|
agent: "guard-agent",
|
|
1616
1658
|
responsibleFor: ["Knowledge drift"],
|
|
@@ -1884,13 +1926,15 @@ Generated from Kaddo Context Pack.
|
|
|
1884
1926
|
|
|
1885
1927
|
Save the architecture overview as \`knowledge/tech/current-state.md\`, supporting notes as
|
|
1886
1928
|
\`knowledge/tech/architecture-notes.md\`, and decision candidates as
|
|
1887
|
-
\`knowledge/tech/decision-candidates.md\`.
|
|
1929
|
+
\`knowledge/tech/decision-candidates.md\`. Final ADRs always live under
|
|
1930
|
+
\`knowledge/tech/decisions/\` \u2014 never directly in \`knowledge/tech/\`.
|
|
1888
1931
|
|
|
1889
1932
|
## Quality Checklist
|
|
1890
1933
|
|
|
1891
1934
|
- Every component is backed by evidence from the context pack.
|
|
1892
1935
|
- Assumptions and confidence are explicit.
|
|
1893
1936
|
- No final decisions are asserted \u2014 only candidates.
|
|
1937
|
+
- Final ADRs go to \`knowledge/tech/decisions/\`, not \`knowledge/tech/\`.
|
|
1894
1938
|
- Open questions are listed.
|
|
1895
1939
|
`;
|
|
1896
1940
|
var ROADMAP_AGENT = `# Roadmap Agent
|
|
@@ -2209,12 +2253,16 @@ Generated from Kaddo Context Pack.
|
|
|
2209
2253
|
|
|
2210
2254
|
## Where to Save the Result
|
|
2211
2255
|
|
|
2212
|
-
Save
|
|
2256
|
+
Save decision **candidates** as \`knowledge/tech/decision-candidates.md\`. When a candidate becomes
|
|
2257
|
+
a **final ADR**, it must live under \`knowledge/tech/decisions/\` (one file per decision, e.g.
|
|
2258
|
+
\`knowledge/tech/decisions/ADR-0001-<slug>.md\`) \u2014 **never** directly in \`knowledge/tech/\`.
|
|
2259
|
+
(Decision = the concept \xB7 ADR = the format \xB7 Path = \`knowledge/tech/decisions/\`.)
|
|
2213
2260
|
|
|
2214
2261
|
## Quality Checklist
|
|
2215
2262
|
|
|
2216
2263
|
- Each candidate has context and alternatives.
|
|
2217
2264
|
- No decision is asserted as final.
|
|
2265
|
+
- Final ADRs go to \`knowledge/tech/decisions/\`, never to \`knowledge/tech/\` directly.
|
|
2218
2266
|
- Assumptions are marked.
|
|
2219
2267
|
- Validation needs are explicit.
|
|
2220
2268
|
`;
|
|
@@ -2913,6 +2961,148 @@ Code, tests and migrations live in the repository. Knowledge updates go under \`
|
|
|
2913
2961
|
- Affected knowledge is updated.
|
|
2914
2962
|
- Commit is suggested and awaits human confirmation \u2014 never run automatically.
|
|
2915
2963
|
`;
|
|
2964
|
+
var CAPSULE_AGENT = `# Capsule Agent
|
|
2965
|
+
|
|
2966
|
+
## Role
|
|
2967
|
+
|
|
2968
|
+
You are the Kaddo Capsule Agent. Your job is to refine and validate a **Knowledge Capsule** \u2014 a
|
|
2969
|
+
minimal, portable summary another project can consume as external context \u2014 before it is exported.
|
|
2970
|
+
|
|
2971
|
+
You do not write code, you never invent contracts, and you mark uncertainties. A capsule contains
|
|
2972
|
+
**knowledge, not code or secrets**.
|
|
2973
|
+
|
|
2974
|
+
## When to Use
|
|
2975
|
+
|
|
2976
|
+
Use this agent before sharing a Knowledge Capsule (after \`kaddo capsule export\` produced a draft),
|
|
2977
|
+
to sharpen its purpose, capabilities, public contracts, risks, owners and out-of-scope.
|
|
2978
|
+
|
|
2979
|
+
## Input Required
|
|
2980
|
+
|
|
2981
|
+
Provide \`.kaddo/context-pack.md\` plus \`knowledge/product/capabilities.md\`,
|
|
2982
|
+
\`knowledge/tech/current-state.md\`, \`knowledge/tech/decisions/\` and any contracts
|
|
2983
|
+
(\`knowledge/tech/contracts/\`) that exist. Also provide the draft capsule from
|
|
2984
|
+
\`.kaddo/exports/<system>.capsule.md\`.
|
|
2985
|
+
|
|
2986
|
+
## Expected Output
|
|
2987
|
+
|
|
2988
|
+
A refined Markdown capsule intended to be saved as \`.kaddo/exports/<system>.capsule.md\`.
|
|
2989
|
+
|
|
2990
|
+
## Instructions
|
|
2991
|
+
|
|
2992
|
+
1. Summarize what the system does and the boundaries of this capsule.
|
|
2993
|
+
2. List the **public contracts** consumers integrate with (APIs, events) \u2014 never invent them.
|
|
2994
|
+
3. List exposed capabilities, dependencies and known integration risks.
|
|
2995
|
+
4. Identify owners and relevant ADRs.
|
|
2996
|
+
5. State what is **out of scope** for this capsule.
|
|
2997
|
+
6. Mark any unknowns explicitly.
|
|
2998
|
+
|
|
2999
|
+
## Constraints
|
|
3000
|
+
|
|
3001
|
+
- Do **not** export secrets, tokens, credentials, private keys, PII or internal sensitive URLs.
|
|
3002
|
+
- Do **not** export source code.
|
|
3003
|
+
- Do **not** invent contracts or integrations.
|
|
3004
|
+
- Summarize and mark boundaries; prefer "unknown" over guessing.
|
|
3005
|
+
|
|
3006
|
+
## Output Format
|
|
3007
|
+
|
|
3008
|
+
\`\`\`markdown
|
|
3009
|
+
---
|
|
3010
|
+
type: knowledge-capsule
|
|
3011
|
+
system: <system>
|
|
3012
|
+
version: 1
|
|
3013
|
+
updated_at: <YYYY-MM-DD>
|
|
3014
|
+
owner: <team>
|
|
3015
|
+
---
|
|
3016
|
+
|
|
3017
|
+
# <System> \u2014 Knowledge Capsule
|
|
3018
|
+
|
|
3019
|
+
## Purpose
|
|
3020
|
+
## Responsibilities
|
|
3021
|
+
## Exposed Capabilities
|
|
3022
|
+
## Public Contracts
|
|
3023
|
+
## Dependencies
|
|
3024
|
+
## Known Risks
|
|
3025
|
+
## Relevant ADRs
|
|
3026
|
+
## Owners
|
|
3027
|
+
## Out of Scope
|
|
3028
|
+
## Usage Notes
|
|
3029
|
+
\`\`\`
|
|
3030
|
+
|
|
3031
|
+
## Where to Save the Result
|
|
3032
|
+
|
|
3033
|
+
Save as \`.kaddo/exports/<system>.capsule.md\`. The human reviews the security checklist (no
|
|
3034
|
+
secrets, no source) before sharing.
|
|
3035
|
+
|
|
3036
|
+
## Quality Checklist
|
|
3037
|
+
|
|
3038
|
+
- Purpose and boundaries are clear.
|
|
3039
|
+
- Public contracts are real (not invented) \u2014 unknowns are marked.
|
|
3040
|
+
- Capabilities, dependencies, risks, owners and out-of-scope are present.
|
|
3041
|
+
- No secrets, credentials, PII or source code are included.
|
|
3042
|
+
`;
|
|
3043
|
+
var OWNERSHIP_AGENT = `# Ownership Agent
|
|
3044
|
+
|
|
3045
|
+
## Role
|
|
3046
|
+
|
|
3047
|
+
You are the Kaddo Ownership Agent. Your job is to propose **precise** \`code:\` ownership globs for
|
|
3048
|
+
Work Items and knowledge artifacts, so Guard can relate code changes to the right knowledge.
|
|
3049
|
+
|
|
3050
|
+
You do not write code, you do not modify files, and you never run Git. You propose; the human
|
|
3051
|
+
confirms and applies (with \`kaddo owners suggest\`).
|
|
3052
|
+
|
|
3053
|
+
## When to Use
|
|
3054
|
+
|
|
3055
|
+
Use this agent after \`kaddo scan\` and \`kaddo context\`, when Work Items or artifacts are missing
|
|
3056
|
+
\`code:\` ownership, or when existing ownership is too broad or inaccurate.
|
|
3057
|
+
|
|
3058
|
+
## Input Required
|
|
3059
|
+
|
|
3060
|
+
Provide \`.kaddo/context-pack.md\` as the primary input, plus the Work Items under
|
|
3061
|
+
\`knowledge/delivery/work-items/\`, \`knowledge/tech/codebase.md\` and \`knowledge/inventory.md\` when
|
|
3062
|
+
they exist (for the real source structure).
|
|
3063
|
+
|
|
3064
|
+
## Expected Output
|
|
3065
|
+
|
|
3066
|
+
For each artifact, a precise set of \`code:\` globs.
|
|
3067
|
+
|
|
3068
|
+
## Instructions
|
|
3069
|
+
|
|
3070
|
+
1. Map each Work Item / artifact to the smallest set of paths that actually implement it.
|
|
3071
|
+
2. Prefer **narrow** globs (e.g. \`src/payments/**\`) over broad ones (e.g. \`src/**\`).
|
|
3072
|
+
3. Use real paths from the inventory/codebase \u2014 do not invent directories.
|
|
3073
|
+
4. Include relevant root files (e.g. \`package.json\`, \`tsconfig.json\`) when they belong.
|
|
3074
|
+
5. Flag artifacts where ownership is genuinely unclear instead of guessing broadly.
|
|
3075
|
+
|
|
3076
|
+
## Constraints
|
|
3077
|
+
|
|
3078
|
+
- Do not implement code.
|
|
3079
|
+
- Do not modify files without confirmation \u2014 propose globs for the human to apply.
|
|
3080
|
+
- Do not create branches or commits; never run Git.
|
|
3081
|
+
- Prefer precision: broad globs reduce Guard usefulness.
|
|
3082
|
+
|
|
3083
|
+
## Output Format
|
|
3084
|
+
|
|
3085
|
+
\`\`\`yaml
|
|
3086
|
+
# <Work Item id> \u2014 proposed ownership
|
|
3087
|
+
code:
|
|
3088
|
+
- package.json
|
|
3089
|
+
- tsconfig.json
|
|
3090
|
+
- src/cli/**
|
|
3091
|
+
- src/shared/**
|
|
3092
|
+
\`\`\`
|
|
3093
|
+
|
|
3094
|
+
## Where to Save the Result
|
|
3095
|
+
|
|
3096
|
+
The human applies the proposed globs to the artifact's front matter with \`kaddo owners suggest\`
|
|
3097
|
+
(or by editing the \`code:\` field). This agent does not write files.
|
|
3098
|
+
|
|
3099
|
+
## Quality Checklist
|
|
3100
|
+
|
|
3101
|
+
- Globs are narrow and based on real paths.
|
|
3102
|
+
- No \`src/**\`-style catch-alls unless truly justified.
|
|
3103
|
+
- Unclear ownership is flagged, not guessed.
|
|
3104
|
+
- Output is a proposal for human confirmation \u2014 nothing is applied automatically.
|
|
3105
|
+
`;
|
|
2916
3106
|
var BACKLOG_AGENT = `# Backlog Agent
|
|
2917
3107
|
|
|
2918
3108
|
## Role
|
|
@@ -3024,7 +3214,11 @@ var AGENT_PROMPTS = [
|
|
|
3024
3214
|
// Implementation (the only agent that may suggest a branch — VS-044)
|
|
3025
3215
|
{ fileName: "implementation-agent.md", content: IMPLEMENTATION_AGENT },
|
|
3026
3216
|
// Backlog capture (idea → draft / roadmap candidate — VS-050)
|
|
3027
|
-
{ fileName: "backlog-agent.md", content: BACKLOG_AGENT }
|
|
3217
|
+
{ fileName: "backlog-agent.md", content: BACKLOG_AGENT },
|
|
3218
|
+
// Ownership proposals (precise code: globs — VS-052)
|
|
3219
|
+
{ fileName: "ownership-agent.md", content: OWNERSHIP_AGENT },
|
|
3220
|
+
// Knowledge Capsule refinement (external context — VS-054)
|
|
3221
|
+
{ fileName: "capsule-agent.md", content: CAPSULE_AGENT }
|
|
3028
3222
|
// Every official prompt ends with its responsibility boundaries + Agent Trace footer.
|
|
3029
3223
|
].map((p2) => ({ fileName: p2.fileName, content: withResponsibilityTrace(p2.fileName, p2.content) }));
|
|
3030
3224
|
|
|
@@ -3039,13 +3233,15 @@ var AGENT_GROUPS = {
|
|
|
3039
3233
|
"security-agent.md",
|
|
3040
3234
|
"standards-agent.md",
|
|
3041
3235
|
"module-design-agent.md",
|
|
3042
|
-
"adr-agent.md"
|
|
3236
|
+
"adr-agent.md",
|
|
3237
|
+
"capsule-agent.md"
|
|
3043
3238
|
],
|
|
3044
3239
|
delivery: [
|
|
3045
3240
|
"backlog-agent.md",
|
|
3046
3241
|
"roadmap-agent.md",
|
|
3047
3242
|
"work-item-agent.md",
|
|
3048
3243
|
"implementation-agent.md",
|
|
3244
|
+
"ownership-agent.md",
|
|
3049
3245
|
"git-strategy-agent.md"
|
|
3050
3246
|
],
|
|
3051
3247
|
utilities: ["legacy-agent.md"]
|
|
@@ -3079,7 +3275,8 @@ var RECOMMENDED_BY_STATE = {
|
|
|
3079
3275
|
"roadmap-agent.md",
|
|
3080
3276
|
"backlog-agent.md",
|
|
3081
3277
|
"work-item-agent.md",
|
|
3082
|
-
"implementation-agent.md"
|
|
3278
|
+
"implementation-agent.md",
|
|
3279
|
+
"ownership-agent.md"
|
|
3083
3280
|
],
|
|
3084
3281
|
legacy: [
|
|
3085
3282
|
"legacy-agent.md",
|
|
@@ -3088,7 +3285,8 @@ var RECOMMENDED_BY_STATE = {
|
|
|
3088
3285
|
"roadmap-agent.md",
|
|
3089
3286
|
"backlog-agent.md",
|
|
3090
3287
|
"work-item-agent.md",
|
|
3091
|
-
"implementation-agent.md"
|
|
3288
|
+
"implementation-agent.md",
|
|
3289
|
+
"ownership-agent.md"
|
|
3092
3290
|
]
|
|
3093
3291
|
};
|
|
3094
3292
|
function recommendedAgents(state) {
|
|
@@ -3757,7 +3955,7 @@ function formatList(items) {
|
|
|
3757
3955
|
return items.map((i) => `- ${i.trim()}`).join("\n");
|
|
3758
3956
|
}
|
|
3759
3957
|
function buildFrontMatter(id, type, level, title, answers) {
|
|
3760
|
-
const
|
|
3958
|
+
const today2 = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
3761
3959
|
const lines = [
|
|
3762
3960
|
"---",
|
|
3763
3961
|
`type: ${type}`,
|
|
@@ -3769,7 +3967,7 @@ function buildFrontMatter(id, type, level, title, answers) {
|
|
|
3769
3967
|
`initiative:`,
|
|
3770
3968
|
`domains: []`,
|
|
3771
3969
|
`code: []`,
|
|
3772
|
-
`created_at: ${
|
|
3970
|
+
`created_at: ${today2}`,
|
|
3773
3971
|
`summary: "${answers.problem?.split(".")[0] ?? title}"`,
|
|
3774
3972
|
"---"
|
|
3775
3973
|
];
|
|
@@ -3839,7 +4037,7 @@ _What did we learn from this change? Update after completion._
|
|
|
3839
4037
|
return sections.join("\n");
|
|
3840
4038
|
}
|
|
3841
4039
|
function buildModuleFrontMatter(id, modType, title, answers) {
|
|
3842
|
-
const
|
|
4040
|
+
const today2 = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
3843
4041
|
const extra = modType.extraFrontMatter ?? {};
|
|
3844
4042
|
const extraLines = Object.entries(extra).map(
|
|
3845
4043
|
([k, v]) => `${k}: ${JSON.stringify(v)}`
|
|
@@ -3855,7 +4053,7 @@ function buildModuleFrontMatter(id, modType, title, answers) {
|
|
|
3855
4053
|
`initiative:`,
|
|
3856
4054
|
`domains: []`,
|
|
3857
4055
|
`code: []`,
|
|
3858
|
-
`created_at: ${
|
|
4056
|
+
`created_at: ${today2}`,
|
|
3859
4057
|
`summary: "${title}"`,
|
|
3860
4058
|
...extraLines,
|
|
3861
4059
|
"---"
|
|
@@ -3940,8 +4138,8 @@ async function runCreate(type, opts = {}) {
|
|
|
3940
4138
|
answers[question.frontMatterField] = answer.trim();
|
|
3941
4139
|
}
|
|
3942
4140
|
const id = nextWorkItemId(dir);
|
|
3943
|
-
const
|
|
3944
|
-
const fileName = `${id}-${
|
|
4141
|
+
const slug2 = slugify(title);
|
|
4142
|
+
const fileName = `${id}-${slug2}.md`;
|
|
3945
4143
|
const filePath = join(dir, DRAFT_DIR, fileName);
|
|
3946
4144
|
const frontMatter2 = buildFrontMatter(id, workItemType, level, title.trim(), answers);
|
|
3947
4145
|
const body = buildBody(workItemType, level, title.trim(), answers, levelDef.qualityGate);
|
|
@@ -3981,8 +4179,8 @@ async function runCreateModule(dir, modType) {
|
|
|
3981
4179
|
answers[q.frontMatterField] = answer.trim();
|
|
3982
4180
|
}
|
|
3983
4181
|
const id = nextWorkItemId(dir);
|
|
3984
|
-
const
|
|
3985
|
-
const fileName = `${id}-${
|
|
4182
|
+
const slug2 = slugify(title);
|
|
4183
|
+
const fileName = `${id}-${slug2}.md`;
|
|
3986
4184
|
const filePath = join(dir, DRAFT_DIR, fileName);
|
|
3987
4185
|
const frontMatter2 = buildModuleFrontMatter(id, modType, title.trim(), answers);
|
|
3988
4186
|
const body = buildModuleBody(modType, title.trim(), answers);
|
|
@@ -4004,7 +4202,7 @@ function resolveCandidateLevel(candidate, type) {
|
|
|
4004
4202
|
return getLevelForType(type);
|
|
4005
4203
|
}
|
|
4006
4204
|
function buildRoadmapFrontMatter(id, type, level, title, candidate, answers) {
|
|
4007
|
-
const
|
|
4205
|
+
const today2 = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
4008
4206
|
const summary = (answers.problem?.split(".")[0] ?? candidate.expectedValue ?? title).trim();
|
|
4009
4207
|
const initiative = candidate.initiative?.title ?? candidate.initiative?.id ?? "";
|
|
4010
4208
|
const lines = [
|
|
@@ -4018,7 +4216,7 @@ function buildRoadmapFrontMatter(id, type, level, title, candidate, answers) {
|
|
|
4018
4216
|
`initiative: "${initiative.replace(/"/g, "'")}"`,
|
|
4019
4217
|
`domains: []`,
|
|
4020
4218
|
`code: []`,
|
|
4021
|
-
`created_at: ${
|
|
4219
|
+
`created_at: ${today2}`,
|
|
4022
4220
|
`source: roadmap`,
|
|
4023
4221
|
`source_id: ${candidate.id}`,
|
|
4024
4222
|
`source_initiative: ${candidate.initiative?.id ?? "unknown"}`,
|
|
@@ -4114,8 +4312,8 @@ function buildRoadmapWorkItem(opts) {
|
|
|
4114
4312
|
const { id, type, level, candidate } = opts;
|
|
4115
4313
|
const answers = opts.answers ?? {};
|
|
4116
4314
|
const title = candidate.title.trim();
|
|
4117
|
-
const
|
|
4118
|
-
const fileName = `${id}-${
|
|
4315
|
+
const slug2 = slugify(title);
|
|
4316
|
+
const fileName = `${id}-${slug2}.md`;
|
|
4119
4317
|
const qualityGate = getLevel(level).qualityGate;
|
|
4120
4318
|
const frontMatter2 = buildRoadmapFrontMatter(id, type, level, title, candidate, answers);
|
|
4121
4319
|
const body = buildRoadmapBody(type, level, title, candidate, answers, qualityGate);
|
|
@@ -4234,6 +4432,14 @@ async function isGitRepo(dir) {
|
|
|
4234
4432
|
return false;
|
|
4235
4433
|
}
|
|
4236
4434
|
}
|
|
4435
|
+
async function getUntrackedFiles() {
|
|
4436
|
+
try {
|
|
4437
|
+
const result = await execa("git", ["ls-files", "--others", "--exclude-standard"]);
|
|
4438
|
+
return result.stdout.split("\n").map((f) => f.trim()).filter(Boolean);
|
|
4439
|
+
} catch {
|
|
4440
|
+
return [];
|
|
4441
|
+
}
|
|
4442
|
+
}
|
|
4237
4443
|
|
|
4238
4444
|
// src/services/artifact-reader.ts
|
|
4239
4445
|
import matter from "gray-matter";
|
|
@@ -4254,7 +4460,8 @@ function parseArtifact(filePath, raw) {
|
|
|
4254
4460
|
status: String(data.status ?? ""),
|
|
4255
4461
|
phase: String(data.phase ?? ""),
|
|
4256
4462
|
initiative: String(data.initiative ?? data.source_initiative ?? ""),
|
|
4257
|
-
source: data.source ? String(data.source) : ""
|
|
4463
|
+
source: data.source ? String(data.source) : "",
|
|
4464
|
+
sourceId: String(data.source_id ?? "")
|
|
4258
4465
|
};
|
|
4259
4466
|
} catch {
|
|
4260
4467
|
return null;
|
|
@@ -4892,6 +5099,15 @@ async function runGuard(opts = {}) {
|
|
|
4892
5099
|
const plugins = resolvePlugins(config.plugins ?? []);
|
|
4893
5100
|
const mode = opts.staged ? "staged" : "head";
|
|
4894
5101
|
const currentFiles = await getModifiedFiles(mode);
|
|
5102
|
+
const untrackedFiles = opts.staged ? [] : await getUntrackedFiles();
|
|
5103
|
+
const printUntracked = () => {
|
|
5104
|
+
if (untrackedFiles.length === 0 || jsonMode) return;
|
|
5105
|
+
console.log("");
|
|
5106
|
+
console.log(`Untracked files detected (${untrackedFiles.length}):`);
|
|
5107
|
+
for (const f of untrackedFiles.slice(0, 10)) console.log(` - ${f}`);
|
|
5108
|
+
if (untrackedFiles.length > 10) console.log(` \u2026and ${untrackedFiles.length - 10} more`);
|
|
5109
|
+
console.log("Guard may not fully evaluate these files until they are tracked. (FYI \u2014 non-blocking)");
|
|
5110
|
+
};
|
|
4895
5111
|
let workspaceScan = null;
|
|
4896
5112
|
if (opts.workspace) {
|
|
4897
5113
|
workspaceScan = await collectWorkspaceChanges(dir, mode);
|
|
@@ -4901,6 +5117,7 @@ async function runGuard(opts = {}) {
|
|
|
4901
5117
|
if (touchedFiles.length === 0) {
|
|
4902
5118
|
if (workspaceScan) printWorkspaceHeader(workspaceScan);
|
|
4903
5119
|
console.log("kaddo guard: no modified files detected.");
|
|
5120
|
+
printUntracked();
|
|
4904
5121
|
return;
|
|
4905
5122
|
}
|
|
4906
5123
|
const archDir = join(dir, ARCH_DIR3);
|
|
@@ -4940,10 +5157,12 @@ async function runGuard(opts = {}) {
|
|
|
4940
5157
|
if (workspaceScan) printWorkspaceHeader(workspaceScan);
|
|
4941
5158
|
printHeader(touchedFiles);
|
|
4942
5159
|
console.log(" No artifact ownership matches found.");
|
|
5160
|
+
printUntracked();
|
|
4943
5161
|
return;
|
|
4944
5162
|
}
|
|
4945
5163
|
if (workspaceScan) printWorkspaceHeader(workspaceScan);
|
|
4946
5164
|
printHeader(touchedFiles);
|
|
5165
|
+
printUntracked();
|
|
4947
5166
|
for (const match of activeMatches) {
|
|
4948
5167
|
printFYI(match, dir);
|
|
4949
5168
|
}
|
|
@@ -5017,8 +5236,8 @@ function runIgnoreRemove(artifactId) {
|
|
|
5017
5236
|
}
|
|
5018
5237
|
|
|
5019
5238
|
// src/commands/explain.ts
|
|
5020
|
-
import
|
|
5021
|
-
import { parse as
|
|
5239
|
+
import matter3 from "gray-matter";
|
|
5240
|
+
import { parse as parseYaml9 } from "yaml";
|
|
5022
5241
|
|
|
5023
5242
|
// src/core/delivery-phase.ts
|
|
5024
5243
|
function layer(layers, name) {
|
|
@@ -5069,22 +5288,30 @@ function assessPhase(input) {
|
|
|
5069
5288
|
const firstOf = (s) => items.find((i) => i.lifecycle === s);
|
|
5070
5289
|
let recommendedAgents2 = [];
|
|
5071
5290
|
let nextStep = "";
|
|
5291
|
+
let llmInstructions = [];
|
|
5072
5292
|
switch (phase) {
|
|
5073
5293
|
case "Discovery": {
|
|
5074
5294
|
const m = firstMissingLayerAgent(input.layers);
|
|
5075
5295
|
recommendedAgents2 = [m.agent];
|
|
5076
5296
|
nextStep = m.step;
|
|
5297
|
+
llmInstructions = [`Use the ${m.agent} to fill the missing base knowledge.`, "Do not write code."];
|
|
5077
5298
|
break;
|
|
5078
5299
|
}
|
|
5079
5300
|
case "Planning": {
|
|
5080
5301
|
recommendedAgents2 = ["roadmap-agent"];
|
|
5081
5302
|
nextStep = "Use roadmap-agent to create knowledge/delivery/roadmap.md";
|
|
5303
|
+
llmInstructions = ["Use the roadmap-agent.", "Do not write code.", "Generate roadmap candidates."];
|
|
5082
5304
|
break;
|
|
5083
5305
|
}
|
|
5084
5306
|
case "Delivery Preparation": {
|
|
5085
5307
|
recommendedAgents2 = ["kaddo create --from roadmap", "work-item-agent"];
|
|
5086
5308
|
const n = input.roadmap.remaining || input.roadmap.candidates;
|
|
5087
5309
|
nextStep = `Run \`kaddo create --from roadmap\`${n ? ` (${n} candidate(s))` : ""}, then refine with work-item-agent`;
|
|
5310
|
+
llmInstructions = [
|
|
5311
|
+
"Materialize roadmap candidates with `kaddo create --from roadmap`.",
|
|
5312
|
+
"Use the work-item-agent to refine them.",
|
|
5313
|
+
"Do not implement yet."
|
|
5314
|
+
];
|
|
5088
5315
|
break;
|
|
5089
5316
|
}
|
|
5090
5317
|
case "Active Delivery": {
|
|
@@ -5092,21 +5319,41 @@ function assessPhase(input) {
|
|
|
5092
5319
|
const wi = firstOf("ready");
|
|
5093
5320
|
recommendedAgents2 = ["implementation-agent"];
|
|
5094
5321
|
nextStep = wi ? `Start ${wi.id} \u2014 ${wi.title} (ready \u2192 in-progress)` : "Start a ready Work Item";
|
|
5322
|
+
llmInstructions = [
|
|
5323
|
+
"Use the implementation-agent.",
|
|
5324
|
+
"Suggest a branch name only.",
|
|
5325
|
+
"Do not run git commands."
|
|
5326
|
+
];
|
|
5095
5327
|
} else if (bs["in-progress"] > 0) {
|
|
5096
5328
|
const wi = firstOf("in-progress");
|
|
5097
5329
|
recommendedAgents2 = ["implementation-agent", "kaddo scan", "kaddo owners suggest", "kaddo guard"];
|
|
5098
5330
|
nextStep = wi ? `Continue ${wi.id} \u2014 ${wi.title}; then run kaddo scan, owners suggest, guard` : "Continue the in-progress Work Item; then scan, owners suggest, guard";
|
|
5331
|
+
llmInstructions = [
|
|
5332
|
+
"Continue the in-progress Work Item with the implementation-agent.",
|
|
5333
|
+
"After changes, run `kaddo scan`, `kaddo owners suggest` and `kaddo guard`.",
|
|
5334
|
+
"Do not commit, push or merge without explicit human confirmation."
|
|
5335
|
+
];
|
|
5099
5336
|
} else if (bs.draft > 0) {
|
|
5100
5337
|
const wi = firstOf("draft");
|
|
5101
5338
|
recommendedAgents2 = ["work-item-agent"];
|
|
5102
5339
|
nextStep = wi ? `Refine ${wi.id} from draft to ready` : "Refine a draft Work Item to ready";
|
|
5340
|
+
llmInstructions = [
|
|
5341
|
+
"Refine draft Work Items to ready.",
|
|
5342
|
+
"Use the work-item-agent.",
|
|
5343
|
+
"Do not implement unless the user explicitly asks."
|
|
5344
|
+
];
|
|
5103
5345
|
} else if (bs.blocked > 0) {
|
|
5104
5346
|
const wi = firstOf("blocked");
|
|
5105
5347
|
recommendedAgents2 = ["work-item-agent"];
|
|
5106
5348
|
nextStep = wi ? `Resolve the blocker on ${wi.id} \u2014 ${wi.title}` : "Resolve the blockers on active work";
|
|
5349
|
+
llmInstructions = [
|
|
5350
|
+
"Resolve the blockers with the work-item-agent.",
|
|
5351
|
+
"Do not implement blocked work."
|
|
5352
|
+
];
|
|
5107
5353
|
}
|
|
5108
5354
|
if (input.ownership.workItemsMissingOwnership > 0) {
|
|
5109
5355
|
recommendedAgents2.push("kaddo owners suggest");
|
|
5356
|
+
llmInstructions.push("Ownership is incomplete \u2014 propose `code:` globs (run `kaddo owners suggest`).");
|
|
5110
5357
|
}
|
|
5111
5358
|
break;
|
|
5112
5359
|
}
|
|
@@ -5114,18 +5361,213 @@ function assessPhase(input) {
|
|
|
5114
5361
|
if (input.roadmap.remaining > 0) {
|
|
5115
5362
|
recommendedAgents2 = ["kaddo create --from roadmap", "work-item-agent"];
|
|
5116
5363
|
nextStep = `Materialize ${input.roadmap.remaining} remaining roadmap candidate(s)`;
|
|
5364
|
+
llmInstructions = ["Materialize the remaining roadmap candidates.", "Do not implement yet."];
|
|
5117
5365
|
} else {
|
|
5118
5366
|
recommendedAgents2 = ["roadmap-agent"];
|
|
5119
5367
|
nextStep = "No active work \u2014 use roadmap-agent to plan the next initiative";
|
|
5368
|
+
llmInstructions = ["No active work.", "Use the roadmap-agent to plan the next initiative."];
|
|
5120
5369
|
}
|
|
5121
5370
|
break;
|
|
5122
5371
|
}
|
|
5123
5372
|
}
|
|
5124
|
-
return { phase, reasons, recommendedAgents: recommendedAgents2, nextStep };
|
|
5373
|
+
return { phase, reasons, recommendedAgents: recommendedAgents2, nextStep, llmInstructions };
|
|
5125
5374
|
}
|
|
5126
5375
|
|
|
5127
|
-
// src/core/
|
|
5376
|
+
// src/core/capsule.ts
|
|
5377
|
+
import matter2 from "gray-matter";
|
|
5378
|
+
import { parse as parseYaml8, stringify as stringifyYaml3 } from "yaml";
|
|
5128
5379
|
var KNOWLEDGE = "knowledge";
|
|
5380
|
+
function today() {
|
|
5381
|
+
return (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
5382
|
+
}
|
|
5383
|
+
function firstParagraph(md) {
|
|
5384
|
+
const body = md.replace(/^---\n[\s\S]*?\n---\n/, "");
|
|
5385
|
+
for (const block of body.split(/\n\s*\n/)) {
|
|
5386
|
+
const t = block.trim();
|
|
5387
|
+
if (t && !t.startsWith("#") && !t.startsWith(">")) return t.replace(/\s+/g, " ");
|
|
5388
|
+
}
|
|
5389
|
+
return "";
|
|
5390
|
+
}
|
|
5391
|
+
function readIf(dir, rel) {
|
|
5392
|
+
const p2 = join(dir, rel);
|
|
5393
|
+
return exists(p2) ? readFile(p2) : null;
|
|
5394
|
+
}
|
|
5395
|
+
function headings(md) {
|
|
5396
|
+
return md.split(/\r?\n/).map((l) => l.match(/^#{2,3}\s+(.+?)\s*$/)).filter((m) => Boolean(m)).map((m) => m[1].trim()).filter((h) => !/^(summary|resumen|overview|risks|owners|out of scope)$/i.test(h));
|
|
5397
|
+
}
|
|
5398
|
+
function buildCapsule(dir, config) {
|
|
5399
|
+
const ownerMap = loadOwners(dir);
|
|
5400
|
+
const owners = [...new Set(Object.values(ownerMap).flat())];
|
|
5401
|
+
const all = discoverKnowledge(dir);
|
|
5402
|
+
const purposeSrc = readIf(dir, `${KNOWLEDGE}/business/business.md`) ?? readIf(dir, `${KNOWLEDGE}/knowledge.md`) ?? "";
|
|
5403
|
+
const capsMd = readIf(dir, `${KNOWLEDGE}/product/capabilities.md`);
|
|
5404
|
+
const risksMd = readIf(dir, `${KNOWLEDGE}/legacy/risks.md`);
|
|
5405
|
+
const adrs = all.filter((a) => a.filePath.replace(/\\/g, "/").includes("/tech/decisions/") && Boolean(a.type)).map((a) => a.title || a.id).filter(Boolean);
|
|
5406
|
+
return {
|
|
5407
|
+
system: config.project.name,
|
|
5408
|
+
version: 1,
|
|
5409
|
+
updatedAt: today(),
|
|
5410
|
+
owner: owners[0] ?? "unknown",
|
|
5411
|
+
sourceProject: config.project.name,
|
|
5412
|
+
purpose: firstParagraph(purposeSrc),
|
|
5413
|
+
responsibilities: [],
|
|
5414
|
+
capabilities: capsMd ? headings(capsMd) : [],
|
|
5415
|
+
contracts: [],
|
|
5416
|
+
dependencies: [],
|
|
5417
|
+
knownRisks: risksMd ? headings(risksMd) : [],
|
|
5418
|
+
adrs,
|
|
5419
|
+
owners,
|
|
5420
|
+
outOfScope: [],
|
|
5421
|
+
usageNotes: []
|
|
5422
|
+
};
|
|
5423
|
+
}
|
|
5424
|
+
function section2(title, items, placeholder) {
|
|
5425
|
+
if (items.length === 0) return `## ${title}
|
|
5426
|
+
|
|
5427
|
+
_${placeholder}_
|
|
5428
|
+
`;
|
|
5429
|
+
return `## ${title}
|
|
5430
|
+
|
|
5431
|
+
${items.map((i) => `- ${i}`).join("\n")}
|
|
5432
|
+
`;
|
|
5433
|
+
}
|
|
5434
|
+
function renderCapsuleMarkdown(c) {
|
|
5435
|
+
const fm = [
|
|
5436
|
+
"---",
|
|
5437
|
+
"type: knowledge-capsule",
|
|
5438
|
+
`system: ${c.system}`,
|
|
5439
|
+
`version: ${c.version}`,
|
|
5440
|
+
`updated_at: ${c.updatedAt}`,
|
|
5441
|
+
`owner: ${c.owner}`,
|
|
5442
|
+
`source_project: ${c.sourceProject}`,
|
|
5443
|
+
...c.sourceCommit ? [`source_commit: ${c.sourceCommit}`] : [],
|
|
5444
|
+
"---"
|
|
5445
|
+
].join("\n");
|
|
5446
|
+
const parts = [
|
|
5447
|
+
fm,
|
|
5448
|
+
"",
|
|
5449
|
+
`# ${c.system} \u2014 Knowledge Capsule`,
|
|
5450
|
+
"",
|
|
5451
|
+
`## Purpose
|
|
5452
|
+
|
|
5453
|
+
${c.purpose || "_To be completed by the capsule-agent._"}
|
|
5454
|
+
`,
|
|
5455
|
+
section2("Responsibilities", c.responsibilities, "List what this system is responsible for."),
|
|
5456
|
+
section2("Exposed Capabilities", c.capabilities, "List the capabilities this system exposes."),
|
|
5457
|
+
section2("Public Contracts", c.contracts, "List public APIs and events (e.g. `POST /orders`, `OrderCreated`)."),
|
|
5458
|
+
section2("Dependencies", c.dependencies, "List systems this one depends on."),
|
|
5459
|
+
section2("Known Risks", c.knownRisks, "List integration risks consumers should know."),
|
|
5460
|
+
section2("Relevant ADRs", c.adrs, "List decisions that affect how to integrate."),
|
|
5461
|
+
section2("Owners", c.owners, "Who owns this system."),
|
|
5462
|
+
section2("Out of Scope", c.outOfScope, "What this capsule deliberately does not cover."),
|
|
5463
|
+
section2("Usage Notes", c.usageNotes, "How consumers should integrate."),
|
|
5464
|
+
"> Security: this capsule must never contain secrets, tokens, credentials, PII or source code.",
|
|
5465
|
+
""
|
|
5466
|
+
];
|
|
5467
|
+
return parts.join("\n");
|
|
5468
|
+
}
|
|
5469
|
+
function serializeCapsuleJson(c) {
|
|
5470
|
+
return JSON.stringify({ type: "knowledge-capsule", ...c }, null, 2) + "\n";
|
|
5471
|
+
}
|
|
5472
|
+
var EXTERNAL_PATH = ".kaddo/external.yml";
|
|
5473
|
+
function loadExternalRegistry(dir) {
|
|
5474
|
+
const p2 = join(dir, EXTERNAL_PATH);
|
|
5475
|
+
if (!exists(p2)) return [];
|
|
5476
|
+
try {
|
|
5477
|
+
const parsed = parseYaml8(readFile(p2));
|
|
5478
|
+
return Array.isArray(parsed?.external) ? parsed.external : [];
|
|
5479
|
+
} catch {
|
|
5480
|
+
return [];
|
|
5481
|
+
}
|
|
5482
|
+
}
|
|
5483
|
+
function serializeExternalRegistry(entries) {
|
|
5484
|
+
return stringifyYaml3({ external: entries });
|
|
5485
|
+
}
|
|
5486
|
+
function sectionList(md, title) {
|
|
5487
|
+
const re = new RegExp(`^##\\s+${title}\\s*$`, "im");
|
|
5488
|
+
const lines = md.split(/\r?\n/);
|
|
5489
|
+
const start = lines.findIndex((l) => re.test(l));
|
|
5490
|
+
if (start < 0) return [];
|
|
5491
|
+
const out = [];
|
|
5492
|
+
for (let i = start + 1; i < lines.length; i++) {
|
|
5493
|
+
if (/^##\s+/.test(lines[i])) break;
|
|
5494
|
+
const m = lines[i].match(/^\s*-\s+(.+?)\s*$/);
|
|
5495
|
+
if (m && !/^_.*_$/.test(m[1])) out.push(m[1].trim());
|
|
5496
|
+
}
|
|
5497
|
+
return out;
|
|
5498
|
+
}
|
|
5499
|
+
function sectionParagraph(md, title) {
|
|
5500
|
+
const re = new RegExp(`^##\\s+${title}\\s*$`, "im");
|
|
5501
|
+
const lines = md.split(/\r?\n/);
|
|
5502
|
+
const start = lines.findIndex((l) => re.test(l));
|
|
5503
|
+
if (start < 0) return "";
|
|
5504
|
+
for (let i = start + 1; i < lines.length; i++) {
|
|
5505
|
+
if (/^##\s+/.test(lines[i])) break;
|
|
5506
|
+
const t = lines[i].trim();
|
|
5507
|
+
if (t && !/^_.*_$/.test(t)) return t;
|
|
5508
|
+
}
|
|
5509
|
+
return "";
|
|
5510
|
+
}
|
|
5511
|
+
function parseCapsule(id, path5, md) {
|
|
5512
|
+
const { data } = matter2(md);
|
|
5513
|
+
const updatedAt = data.updated_at ? String(data.updated_at) : void 0;
|
|
5514
|
+
let ageDays = null;
|
|
5515
|
+
if (updatedAt) {
|
|
5516
|
+
const d = Date.parse(updatedAt);
|
|
5517
|
+
if (!Number.isNaN(d)) ageDays = Math.max(0, Math.floor((Date.now() - d) / 864e5));
|
|
5518
|
+
}
|
|
5519
|
+
return {
|
|
5520
|
+
id,
|
|
5521
|
+
path: path5,
|
|
5522
|
+
system: data.system ? String(data.system) : id,
|
|
5523
|
+
owner: data.owner ? String(data.owner) : void 0,
|
|
5524
|
+
updatedAt,
|
|
5525
|
+
purpose: sectionParagraph(md, "Purpose"),
|
|
5526
|
+
capabilities: sectionList(md, "Exposed Capabilities"),
|
|
5527
|
+
contracts: sectionList(md, "Public Contracts"),
|
|
5528
|
+
knownRisks: sectionList(md, "Known Risks"),
|
|
5529
|
+
ageDays
|
|
5530
|
+
};
|
|
5531
|
+
}
|
|
5532
|
+
function slugId(s) {
|
|
5533
|
+
return s.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "external";
|
|
5534
|
+
}
|
|
5535
|
+
function addExternalCapsule(dir, sourceFile) {
|
|
5536
|
+
if (!exists(sourceFile)) throw new Error(`Capsule not found: ${sourceFile}`);
|
|
5537
|
+
const raw = readFile(sourceFile);
|
|
5538
|
+
const { data } = matter2(raw);
|
|
5539
|
+
const isCapsuleType = data.type === "knowledge-capsule";
|
|
5540
|
+
const fallbackName = sourceFile.split(/[\\/]/).pop()?.replace(/\.capsule\.md$/, "") ?? "external";
|
|
5541
|
+
const id = slugId(String(data.system ?? fallbackName));
|
|
5542
|
+
const destRel = join("external", `${id}.capsule.md`).replace(/\\/g, "/");
|
|
5543
|
+
writeFile(join(dir, destRel), raw);
|
|
5544
|
+
const entry = {
|
|
5545
|
+
id,
|
|
5546
|
+
type: "knowledge-capsule",
|
|
5547
|
+
path: destRel,
|
|
5548
|
+
owner: data.owner ? String(data.owner) : void 0,
|
|
5549
|
+
lastImportedAt: (/* @__PURE__ */ new Date()).toISOString().split("T")[0]
|
|
5550
|
+
};
|
|
5551
|
+
const registry = loadExternalRegistry(dir).filter((e) => e.id !== id);
|
|
5552
|
+
registry.push(entry);
|
|
5553
|
+
writeFile(join(dir, EXTERNAL_PATH), serializeExternalRegistry(registry));
|
|
5554
|
+
return { id, destRel, entry, isCapsuleType };
|
|
5555
|
+
}
|
|
5556
|
+
function loadExternalCapsules(dir) {
|
|
5557
|
+
const out = [];
|
|
5558
|
+
for (const entry of loadExternalRegistry(dir)) {
|
|
5559
|
+
const full = join(dir, entry.path);
|
|
5560
|
+
if (!exists(full) || !isFile(full)) continue;
|
|
5561
|
+
try {
|
|
5562
|
+
out.push(parseCapsule(entry.id, entry.path, readFile(full)));
|
|
5563
|
+
} catch {
|
|
5564
|
+
}
|
|
5565
|
+
}
|
|
5566
|
+
return out;
|
|
5567
|
+
}
|
|
5568
|
+
|
|
5569
|
+
// src/core/knowledge-discovery.ts
|
|
5570
|
+
var KNOWLEDGE2 = "knowledge";
|
|
5129
5571
|
var CONSOLIDATED_TYPE = {
|
|
5130
5572
|
Business: "business",
|
|
5131
5573
|
Product: "product",
|
|
@@ -5167,10 +5609,10 @@ function layerForType(type) {
|
|
|
5167
5609
|
}
|
|
5168
5610
|
function layerFromPath(filePath) {
|
|
5169
5611
|
const p2 = filePath.replace(/\\/g, "/");
|
|
5170
|
-
if (p2.includes(`/${
|
|
5171
|
-
if (p2.includes(`/${
|
|
5172
|
-
if (p2.includes(`/${
|
|
5173
|
-
if (p2.includes(`/${
|
|
5612
|
+
if (p2.includes(`/${KNOWLEDGE2}/business/`)) return "Business";
|
|
5613
|
+
if (p2.includes(`/${KNOWLEDGE2}/product/`)) return "Product";
|
|
5614
|
+
if (p2.includes(`/${KNOWLEDGE2}/tech/`)) return "Tech";
|
|
5615
|
+
if (p2.includes(`/${KNOWLEDGE2}/delivery/`)) return "Delivery";
|
|
5174
5616
|
return null;
|
|
5175
5617
|
}
|
|
5176
5618
|
function basename(p2) {
|
|
@@ -5183,7 +5625,7 @@ function discoverLayers(dir) {
|
|
|
5183
5625
|
Tech: blank(),
|
|
5184
5626
|
Delivery: blank()
|
|
5185
5627
|
};
|
|
5186
|
-
const archDir = join(dir,
|
|
5628
|
+
const archDir = join(dir, KNOWLEDGE2);
|
|
5187
5629
|
const artifacts = exists(archDir) ? readArtifacts(archDir) : [];
|
|
5188
5630
|
for (const a of artifacts) {
|
|
5189
5631
|
const type = a.type;
|
|
@@ -5205,7 +5647,7 @@ function discoverLayers(dir) {
|
|
|
5205
5647
|
}
|
|
5206
5648
|
if (type === "adr" || type === "decision") slot.hasDecision = true;
|
|
5207
5649
|
}
|
|
5208
|
-
if (existsDirWithMd(join(dir,
|
|
5650
|
+
if (existsDirWithMd(join(dir, KNOWLEDGE2, "tech", "decisions"))) acc.Tech.structured = true;
|
|
5209
5651
|
return ["Business", "Product", "Tech", "Delivery"].map((layer2) => ({
|
|
5210
5652
|
layer: layer2,
|
|
5211
5653
|
status: statusFor(layer2, acc[layer2]),
|
|
@@ -5258,6 +5700,30 @@ function renderLayersMarkdown(layers) {
|
|
|
5258
5700
|
|
|
5259
5701
|
// src/core/project-explain.ts
|
|
5260
5702
|
var ARCH_DIR4 = "knowledge";
|
|
5703
|
+
function normalizeTitle(t) {
|
|
5704
|
+
return t.toLowerCase().normalize("NFD").replace(/[̀-ͯ]/g, "").replace(/[^a-z0-9]+/g, " ").trim();
|
|
5705
|
+
}
|
|
5706
|
+
function findDuplicateWorkItems(items) {
|
|
5707
|
+
const groups = [];
|
|
5708
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5709
|
+
const bucket = (key, reason, pred) => {
|
|
5710
|
+
const matched = items.filter(pred);
|
|
5711
|
+
if (matched.length > 1) {
|
|
5712
|
+
const id = `${reason}:${key}:${matched.map((m) => m.id).sort().join(",")}`;
|
|
5713
|
+
if (!seen.has(id)) {
|
|
5714
|
+
seen.add(id);
|
|
5715
|
+
groups.push({ reason, items: matched.map((m) => ({ id: m.id, title: m.title })) });
|
|
5716
|
+
}
|
|
5717
|
+
}
|
|
5718
|
+
};
|
|
5719
|
+
for (const sid of new Set(items.map((i) => i.sourceId).filter(Boolean))) {
|
|
5720
|
+
bucket(sid, `same source candidate (${sid})`, (i) => i.sourceId === sid);
|
|
5721
|
+
}
|
|
5722
|
+
for (const nt of new Set(items.map((i) => normalizeTitle(i.title)).filter(Boolean))) {
|
|
5723
|
+
bucket(nt, "same normalized title", (i) => normalizeTitle(i.title) === nt);
|
|
5724
|
+
}
|
|
5725
|
+
return groups;
|
|
5726
|
+
}
|
|
5261
5727
|
function first(values) {
|
|
5262
5728
|
if (Array.isArray(values)) {
|
|
5263
5729
|
const v = values.find((x) => typeof x === "string" && x);
|
|
@@ -5376,6 +5842,9 @@ function buildProjectExplanation(dir) {
|
|
|
5376
5842
|
workItemsMissingOwnership: items.length - withOwnership
|
|
5377
5843
|
};
|
|
5378
5844
|
const domains = [...new Set(workItemArtifacts.flatMap((a) => a.domains))].filter(Boolean);
|
|
5845
|
+
const duplicateWorkItems = findDuplicateWorkItems(
|
|
5846
|
+
workItemArtifacts.map((a) => ({ id: a.id || a.title, title: a.title, sourceId: a.sourceId }))
|
|
5847
|
+
);
|
|
5379
5848
|
const roadmapPath = join(dir, ARCH_DIR4, "delivery", "roadmap.md");
|
|
5380
5849
|
const roadmapMd = exists(roadmapPath) ? readFile(roadmapPath) : null;
|
|
5381
5850
|
const roadmap = roadmapStats(roadmapMd, items.length);
|
|
@@ -5425,6 +5894,8 @@ function buildProjectExplanation(dir) {
|
|
|
5425
5894
|
workItems,
|
|
5426
5895
|
ownership,
|
|
5427
5896
|
domains,
|
|
5897
|
+
duplicateWorkItems,
|
|
5898
|
+
externalCapsules: loadExternalCapsules(dir),
|
|
5428
5899
|
layers,
|
|
5429
5900
|
roadmap,
|
|
5430
5901
|
mappedModules,
|
|
@@ -5553,11 +6024,31 @@ function renderExplanationHuman(exp) {
|
|
|
5553
6024
|
lines.push("- Mapped modules: 0");
|
|
5554
6025
|
lines.push("");
|
|
5555
6026
|
}
|
|
6027
|
+
if (exp.externalCapsules.length > 0) {
|
|
6028
|
+
lines.push(`## External Knowledge Capsules: ${exp.externalCapsules.length}`);
|
|
6029
|
+
for (const cap of exp.externalCapsules) {
|
|
6030
|
+
const owner = cap.owner ? ` \u2014 owner: ${cap.owner}` : "";
|
|
6031
|
+
lines.push(`- ${cap.system}${owner}`);
|
|
6032
|
+
if (cap.ageDays !== null && cap.ageDays >= 90) {
|
|
6033
|
+
lines.push(` \u26A0 capsule last updated ${cap.ageDays} days ago \u2014 it may be stale.`);
|
|
6034
|
+
}
|
|
6035
|
+
}
|
|
6036
|
+
lines.push("");
|
|
6037
|
+
}
|
|
5556
6038
|
if (exp.missingKnowledge.length > 0) {
|
|
5557
6039
|
lines.push("## Missing Knowledge");
|
|
5558
6040
|
for (const m of exp.missingKnowledge) lines.push(`- ${m}`);
|
|
5559
6041
|
lines.push("");
|
|
5560
6042
|
}
|
|
6043
|
+
if (exp.duplicateWorkItems.length > 0) {
|
|
6044
|
+
lines.push("## Possible Duplicate Work Items");
|
|
6045
|
+
for (const g of exp.duplicateWorkItems) {
|
|
6046
|
+
lines.push(`- ${g.reason}:`);
|
|
6047
|
+
for (const i of g.items) lines.push(` - ${i.id} \u2014 ${i.title}`);
|
|
6048
|
+
}
|
|
6049
|
+
lines.push("Review before continuing (non-blocking).");
|
|
6050
|
+
lines.push("");
|
|
6051
|
+
}
|
|
5561
6052
|
const assessment = assessPhase(exp);
|
|
5562
6053
|
lines.push("## Phase");
|
|
5563
6054
|
lines.push(`- Phase: ${assessment.phase}`);
|
|
@@ -5587,7 +6078,7 @@ function readKnowledge(dir) {
|
|
|
5587
6078
|
if (!exists(knowledgePath)) return null;
|
|
5588
6079
|
try {
|
|
5589
6080
|
const raw = readFile(knowledgePath);
|
|
5590
|
-
const { data, content } =
|
|
6081
|
+
const { data, content } = matter3(raw);
|
|
5591
6082
|
return { content, data };
|
|
5592
6083
|
} catch {
|
|
5593
6084
|
return null;
|
|
@@ -5598,7 +6089,7 @@ function readRoadmap(dir) {
|
|
|
5598
6089
|
if (!exists(roadmapPath)) return null;
|
|
5599
6090
|
try {
|
|
5600
6091
|
const raw = readFile(roadmapPath);
|
|
5601
|
-
const { content } =
|
|
6092
|
+
const { content } = matter3(raw);
|
|
5602
6093
|
return content.trim();
|
|
5603
6094
|
} catch {
|
|
5604
6095
|
return null;
|
|
@@ -5608,7 +6099,7 @@ function readConfig(dir) {
|
|
|
5608
6099
|
const configPath = join(dir, CONFIG_PATH3);
|
|
5609
6100
|
if (!exists(configPath)) return {};
|
|
5610
6101
|
try {
|
|
5611
|
-
return
|
|
6102
|
+
return parseYaml9(readFile(configPath));
|
|
5612
6103
|
} catch {
|
|
5613
6104
|
return {};
|
|
5614
6105
|
}
|
|
@@ -5694,8 +6185,8 @@ function explainForAgent(dir, artifacts, opts) {
|
|
|
5694
6185
|
since: opts.since ?? null
|
|
5695
6186
|
};
|
|
5696
6187
|
if (knowledge) {
|
|
5697
|
-
const
|
|
5698
|
-
output.knowledge_summary =
|
|
6188
|
+
const firstParagraph3 = knowledge.content.trim().split("\n\n").find((p2) => p2.trim() && !p2.startsWith("#"));
|
|
6189
|
+
output.knowledge_summary = firstParagraph3?.trim() ?? "";
|
|
5699
6190
|
}
|
|
5700
6191
|
const mappedArtifacts = artifacts.filter((a) => a.type !== "current-state" && a.type !== "roadmap").map((a) => ({
|
|
5701
6192
|
id: a.id,
|
|
@@ -5767,7 +6258,7 @@ function runExplain(opts) {
|
|
|
5767
6258
|
}
|
|
5768
6259
|
|
|
5769
6260
|
// src/core/context-pack.ts
|
|
5770
|
-
import
|
|
6261
|
+
import matter4 from "gray-matter";
|
|
5771
6262
|
var CONTEXT_PACK_VERSION = "1";
|
|
5772
6263
|
var ARCH_DIR6 = "knowledge";
|
|
5773
6264
|
function readScanJson(dir) {
|
|
@@ -5779,8 +6270,8 @@ function readScanJson(dir) {
|
|
|
5779
6270
|
return null;
|
|
5780
6271
|
}
|
|
5781
6272
|
}
|
|
5782
|
-
function
|
|
5783
|
-
const body =
|
|
6273
|
+
function firstParagraph2(markdown) {
|
|
6274
|
+
const body = matter4(markdown).content.trim();
|
|
5784
6275
|
const para = body.split("\n\n").map((p2) => p2.trim()).find((p2) => p2 && !p2.startsWith("#"));
|
|
5785
6276
|
return para ?? "";
|
|
5786
6277
|
}
|
|
@@ -5788,7 +6279,7 @@ function readMarkdownSummary(dir, file) {
|
|
|
5788
6279
|
const p2 = join(dir, ARCH_DIR6, file);
|
|
5789
6280
|
if (!exists(p2)) return null;
|
|
5790
6281
|
try {
|
|
5791
|
-
return
|
|
6282
|
+
return firstParagraph2(readFile(p2));
|
|
5792
6283
|
} catch {
|
|
5793
6284
|
return null;
|
|
5794
6285
|
}
|
|
@@ -5950,12 +6441,15 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
|
|
|
5950
6441
|
roadmap,
|
|
5951
6442
|
phase,
|
|
5952
6443
|
deliveryMix,
|
|
6444
|
+
external: loadExternalCapsules(dir),
|
|
5953
6445
|
mappedModules,
|
|
5954
6446
|
missing,
|
|
6447
|
+
// VS-052: the handoff is driven by the REAL phase, not project.state, so the pack never
|
|
6448
|
+
// contradicts the Current Phase block above it.
|
|
5955
6449
|
handoff: {
|
|
5956
|
-
recommendedAgents: recommendedAgentsForState(state),
|
|
5957
|
-
nextSteps: nextStepsForState2(state),
|
|
5958
|
-
instructions: LLM_INSTRUCTIONS,
|
|
6450
|
+
recommendedAgents: phase.recommendedAgents.length > 0 ? phase.recommendedAgents : recommendedAgentsForState(state),
|
|
6451
|
+
nextSteps: phase.nextStep ? [phase.nextStep] : nextStepsForState2(state),
|
|
6452
|
+
instructions: phase.llmInstructions.length > 0 ? phase.llmInstructions : LLM_INSTRUCTIONS,
|
|
5959
6453
|
operatingRules: OPERATING_RULES
|
|
5960
6454
|
}
|
|
5961
6455
|
};
|
|
@@ -5965,9 +6459,6 @@ function serializeContextPackJson(pack) {
|
|
|
5965
6459
|
}
|
|
5966
6460
|
|
|
5967
6461
|
// src/templates/context-pack-template.ts
|
|
5968
|
-
function stateLabel2(state) {
|
|
5969
|
-
return state === "pre-ai" ? "pre-AI" : state;
|
|
5970
|
-
}
|
|
5971
6462
|
function renderContextPack(pack) {
|
|
5972
6463
|
const { project, scan: scan2, knowledge, missing, handoff } = pack;
|
|
5973
6464
|
const parts = [];
|
|
@@ -6101,6 +6592,21 @@ function renderContextPack(pack) {
|
|
|
6101
6592
|
"Note: Kaddo does not scan secondary repositories during `context`. Mapped modules come from `.kaddo/modules.yml` and module artifacts only.\n"
|
|
6102
6593
|
);
|
|
6103
6594
|
}
|
|
6595
|
+
if (pack.external.length > 0) {
|
|
6596
|
+
parts.push("## External Knowledge\n");
|
|
6597
|
+
parts.push(
|
|
6598
|
+
"Imported Knowledge Capsules \u2014 minimal context about external systems (not their source).\n"
|
|
6599
|
+
);
|
|
6600
|
+
for (const cap of pack.external) {
|
|
6601
|
+
const lines = [`### ${cap.system}`, ""];
|
|
6602
|
+
if (cap.purpose) lines.push(`- Purpose: ${cap.purpose}`);
|
|
6603
|
+
if (cap.capabilities.length) lines.push(`- Capabilities: ${cap.capabilities.join(", ")}`);
|
|
6604
|
+
if (cap.contracts.length) lines.push(`- Contracts: ${cap.contracts.join(", ")}`);
|
|
6605
|
+
if (cap.owner) lines.push(`- Owner: ${cap.owner}`);
|
|
6606
|
+
if (cap.knownRisks.length) lines.push(`- Known risks: ${cap.knownRisks.join("; ")}`);
|
|
6607
|
+
parts.push(lines.join("\n") + "\n");
|
|
6608
|
+
}
|
|
6609
|
+
}
|
|
6104
6610
|
parts.push("## Missing Context\n");
|
|
6105
6611
|
if (missing.length > 0) {
|
|
6106
6612
|
parts.push(missing.map((m) => `- ${m}`).join("\n") + "\n");
|
|
@@ -6108,11 +6614,11 @@ function renderContextPack(pack) {
|
|
|
6108
6614
|
parts.push("_None \u2014 all expected context is present._\n");
|
|
6109
6615
|
}
|
|
6110
6616
|
parts.push("## Recommended Agent Handoff\n");
|
|
6111
|
-
parts.push(`Recommended
|
|
6617
|
+
parts.push(`Recommended next for the **${pack.phase.phase}** phase:
|
|
6112
6618
|
`);
|
|
6113
6619
|
parts.push(handoff.recommendedAgents.map((a, i) => `${i + 1}. ${a}`).join("\n") + "\n");
|
|
6114
6620
|
if (handoff.nextSteps.length > 0) {
|
|
6115
|
-
parts.push("Next
|
|
6621
|
+
parts.push("Next step:\n");
|
|
6116
6622
|
parts.push(handoff.nextSteps.map((s) => `- ${s}`).join("\n") + "\n");
|
|
6117
6623
|
}
|
|
6118
6624
|
parts.push("## Instructions for the LLM\n");
|
|
@@ -6211,7 +6717,7 @@ function buildUnderstandPlan(dir, config) {
|
|
|
6211
6717
|
}
|
|
6212
6718
|
|
|
6213
6719
|
// src/templates/understand-template.ts
|
|
6214
|
-
function
|
|
6720
|
+
function stateLabel2(state) {
|
|
6215
6721
|
return state === "pre-ai" ? "pre-AI" : state;
|
|
6216
6722
|
}
|
|
6217
6723
|
function agentName(file) {
|
|
@@ -6234,7 +6740,7 @@ function renderUnderstand(plan) {
|
|
|
6234
6740
|
].join("\n") + "\n"
|
|
6235
6741
|
);
|
|
6236
6742
|
parts.push("## Recommended Agent Flow\n");
|
|
6237
|
-
parts.push(`Recommended order for a ${
|
|
6743
|
+
parts.push(`Recommended order for a ${stateLabel2(project.state)} project:
|
|
6238
6744
|
`);
|
|
6239
6745
|
parts.push(
|
|
6240
6746
|
steps.map((s, i) => {
|
|
@@ -6316,7 +6822,7 @@ function renderUnderstandTerminal(plan) {
|
|
|
6316
6822
|
}
|
|
6317
6823
|
|
|
6318
6824
|
// src/core/delivery.ts
|
|
6319
|
-
import { parse as
|
|
6825
|
+
import { parse as parseYaml10 } from "yaml";
|
|
6320
6826
|
function slugify2(s) {
|
|
6321
6827
|
return s.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
6322
6828
|
}
|
|
@@ -6422,6 +6928,14 @@ function runUnderstand() {
|
|
|
6422
6928
|
if (assessment.nextStep) {
|
|
6423
6929
|
console.log(`Next step: ${assessment.nextStep}`);
|
|
6424
6930
|
}
|
|
6931
|
+
if (exp.externalCapsules.length > 0) {
|
|
6932
|
+
console.log("");
|
|
6933
|
+
console.log("External knowledge:");
|
|
6934
|
+
for (const cap of exp.externalCapsules) {
|
|
6935
|
+
console.log(` - ${cap.system}${cap.owner ? ` (owner: ${cap.owner})` : ""}`);
|
|
6936
|
+
}
|
|
6937
|
+
console.log(" \u2192 Review the relevant capsule before changing integration behavior with it.");
|
|
6938
|
+
}
|
|
6425
6939
|
const active = activeWorkItems(dir);
|
|
6426
6940
|
if (active.length > 0) {
|
|
6427
6941
|
console.log("");
|
|
@@ -6655,14 +7169,14 @@ async function runClassify(opts = {}) {
|
|
|
6655
7169
|
}
|
|
6656
7170
|
|
|
6657
7171
|
// src/commands/status.ts
|
|
6658
|
-
import { parse as
|
|
7172
|
+
import { parse as parseYaml11 } from "yaml";
|
|
6659
7173
|
var ARCH_DIR8 = "knowledge";
|
|
6660
7174
|
var CONFIG_PATH4 = ".kaddo/config.yml";
|
|
6661
7175
|
function loadConfig3(dir) {
|
|
6662
7176
|
const p2 = join(dir, CONFIG_PATH4);
|
|
6663
7177
|
if (!exists(p2)) return {};
|
|
6664
7178
|
try {
|
|
6665
|
-
return
|
|
7179
|
+
return parseYaml11(readFile(p2));
|
|
6666
7180
|
} catch {
|
|
6667
7181
|
return {};
|
|
6668
7182
|
}
|
|
@@ -6719,7 +7233,7 @@ function runStatus() {
|
|
|
6719
7233
|
}
|
|
6720
7234
|
|
|
6721
7235
|
// src/commands/learn.ts
|
|
6722
|
-
import
|
|
7236
|
+
import matter5 from "gray-matter";
|
|
6723
7237
|
var ARCH_DIR9 = "knowledge";
|
|
6724
7238
|
var WORK_ITEMS_DIR2 = "knowledge/delivery/work-items";
|
|
6725
7239
|
function findWorkItemFile(dir, id) {
|
|
@@ -6731,7 +7245,7 @@ function findWorkItemFile(dir, id) {
|
|
|
6731
7245
|
}
|
|
6732
7246
|
function updateWorkItemFile(filePath, learning) {
|
|
6733
7247
|
const raw = readFile(filePath);
|
|
6734
|
-
const { data, content } =
|
|
7248
|
+
const { data, content } = matter5(raw);
|
|
6735
7249
|
data.status = "done";
|
|
6736
7250
|
data.completed_at = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
6737
7251
|
let updatedContent = content;
|
|
@@ -6756,7 +7270,7 @@ ${learning.trim()}
|
|
|
6756
7270
|
${learning.trim()}
|
|
6757
7271
|
`;
|
|
6758
7272
|
}
|
|
6759
|
-
const newRaw =
|
|
7273
|
+
const newRaw = matter5.stringify(updatedContent, data);
|
|
6760
7274
|
writeFile(filePath, newRaw);
|
|
6761
7275
|
}
|
|
6762
7276
|
async function runLearn(artifactId) {
|
|
@@ -6864,13 +7378,13 @@ function runHistory(opts = {}) {
|
|
|
6864
7378
|
}
|
|
6865
7379
|
|
|
6866
7380
|
// src/commands/add.ts
|
|
6867
|
-
import { parse as
|
|
7381
|
+
import { parse as parseYaml12, stringify as stringifyYaml4 } from "yaml";
|
|
6868
7382
|
var CONFIG_PATH5 = ".kaddo/config.yml";
|
|
6869
7383
|
function readProjectState(dir) {
|
|
6870
7384
|
const configPath = join(dir, CONFIG_PATH5);
|
|
6871
7385
|
if (!exists(configPath)) return void 0;
|
|
6872
7386
|
try {
|
|
6873
|
-
const config =
|
|
7387
|
+
const config = parseYaml12(readFile(configPath));
|
|
6874
7388
|
return config.project?.state;
|
|
6875
7389
|
} catch {
|
|
6876
7390
|
return void 0;
|
|
@@ -6888,12 +7402,12 @@ function markModuleInstalled(dir, configKey, moduleName) {
|
|
|
6888
7402
|
const configPath = join(dir, CONFIG_PATH5);
|
|
6889
7403
|
if (!exists(configPath)) return;
|
|
6890
7404
|
try {
|
|
6891
|
-
const config =
|
|
7405
|
+
const config = parseYaml12(readFile(configPath));
|
|
6892
7406
|
config[configKey] = { installed: true, installed_at: (/* @__PURE__ */ new Date()).toISOString().split("T")[0] };
|
|
6893
7407
|
const modules = config.modules ?? [];
|
|
6894
7408
|
if (!modules.includes(moduleName)) modules.push(moduleName);
|
|
6895
7409
|
config.modules = modules;
|
|
6896
|
-
writeFile(configPath,
|
|
7410
|
+
writeFile(configPath, stringifyYaml4(config));
|
|
6897
7411
|
} catch {
|
|
6898
7412
|
}
|
|
6899
7413
|
}
|
|
@@ -6901,7 +7415,7 @@ function isModuleInstalled(dir, configKey) {
|
|
|
6901
7415
|
const configPath = join(dir, CONFIG_PATH5);
|
|
6902
7416
|
if (!exists(configPath)) return false;
|
|
6903
7417
|
try {
|
|
6904
|
-
const config =
|
|
7418
|
+
const config = parseYaml12(readFile(configPath));
|
|
6905
7419
|
const moduleConfig = config[configKey];
|
|
6906
7420
|
return moduleConfig?.installed === true;
|
|
6907
7421
|
} catch {
|
|
@@ -6984,8 +7498,66 @@ function runAdd(moduleName, opts = {}, dir = cwd()) {
|
|
|
6984
7498
|
}
|
|
6985
7499
|
|
|
6986
7500
|
// src/core/ownership-suggest.ts
|
|
6987
|
-
import
|
|
7501
|
+
import matter6 from "gray-matter";
|
|
6988
7502
|
var SCAN_PATH = ".kaddo/scan.json";
|
|
7503
|
+
function normalizeGlob(input) {
|
|
7504
|
+
let g = input.trim().replace(/\\/g, "/");
|
|
7505
|
+
if (!g) return g;
|
|
7506
|
+
if (g.endsWith("/")) return `${g.slice(0, -1)}/**`;
|
|
7507
|
+
if (!g.includes("*")) {
|
|
7508
|
+
const base = g.split("/").pop() ?? "";
|
|
7509
|
+
const looksLikeFile = /\.[A-Za-z0-9]+$/.test(base);
|
|
7510
|
+
if (!looksLikeFile) return `${g}/**`;
|
|
7511
|
+
}
|
|
7512
|
+
return g;
|
|
7513
|
+
}
|
|
7514
|
+
function levenshtein(a, b) {
|
|
7515
|
+
const m = a.length;
|
|
7516
|
+
const n = b.length;
|
|
7517
|
+
const d = Array.from({ length: m + 1 }, (_, i) => [i, ...Array(n).fill(0)]);
|
|
7518
|
+
for (let j = 0; j <= n; j++) d[0][j] = j;
|
|
7519
|
+
for (let i = 1; i <= m; i++) {
|
|
7520
|
+
for (let j = 1; j <= n; j++) {
|
|
7521
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
7522
|
+
d[i][j] = Math.min(d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1] + cost);
|
|
7523
|
+
}
|
|
7524
|
+
}
|
|
7525
|
+
return d[m][n];
|
|
7526
|
+
}
|
|
7527
|
+
function analyzeGlob(dir, input) {
|
|
7528
|
+
const normalized = normalizeGlob(input);
|
|
7529
|
+
const warnings = [];
|
|
7530
|
+
let suggestion;
|
|
7531
|
+
if (/^\*+$/.test(normalized) || /^[^/*]+\/\*\*$/.test(normalized)) {
|
|
7532
|
+
warnings.push(
|
|
7533
|
+
"This glob is broad and may reduce Guard usefulness. Prefer narrower ownership when possible."
|
|
7534
|
+
);
|
|
7535
|
+
}
|
|
7536
|
+
const starIdx = normalized.indexOf("*");
|
|
7537
|
+
const literal = (starIdx >= 0 ? normalized.slice(0, starIdx) : normalized).replace(/\/+$/, "");
|
|
7538
|
+
if (literal && !exists(join(dir, literal))) {
|
|
7539
|
+
const slash = literal.lastIndexOf("/");
|
|
7540
|
+
const parent = slash >= 0 ? literal.slice(0, slash) : "";
|
|
7541
|
+
const target = slash >= 0 ? literal.slice(slash + 1) : literal;
|
|
7542
|
+
const parentFull = parent ? join(dir, parent) : dir;
|
|
7543
|
+
let near;
|
|
7544
|
+
try {
|
|
7545
|
+
if (exists(parentFull)) {
|
|
7546
|
+
near = readDir(parentFull).find(
|
|
7547
|
+
(e) => e !== target && levenshtein(e.toLowerCase(), target.toLowerCase()) <= 2
|
|
7548
|
+
);
|
|
7549
|
+
}
|
|
7550
|
+
} catch {
|
|
7551
|
+
}
|
|
7552
|
+
if (near) {
|
|
7553
|
+
suggestion = `${parent ? parent + "/" : ""}${near}${normalized.slice(literal.length)}`;
|
|
7554
|
+
warnings.push(`Path does not exist. Did you mean ${suggestion}?`);
|
|
7555
|
+
} else {
|
|
7556
|
+
warnings.push(`Path "${literal}" does not exist in the project.`);
|
|
7557
|
+
}
|
|
7558
|
+
}
|
|
7559
|
+
return { normalized, warnings, suggestion };
|
|
7560
|
+
}
|
|
6989
7561
|
function toStringArray3(value) {
|
|
6990
7562
|
return Array.isArray(value) ? value.map((v) => String(v)).filter(Boolean) : [];
|
|
6991
7563
|
}
|
|
@@ -7048,11 +7620,11 @@ function suggestGlobs(artifact, signals) {
|
|
|
7048
7620
|
return [...new Set(out)];
|
|
7049
7621
|
}
|
|
7050
7622
|
function applyOwnership(raw, globs, mode = "replace") {
|
|
7051
|
-
const parsed =
|
|
7623
|
+
const parsed = matter6(raw);
|
|
7052
7624
|
const existing = toStringArray3(parsed.data.code);
|
|
7053
7625
|
const next = mode === "append" ? [.../* @__PURE__ */ new Set([...existing, ...globs])] : [...new Set(globs)];
|
|
7054
7626
|
const data = { ...parsed.data, code: next };
|
|
7055
|
-
return
|
|
7627
|
+
return matter6.stringify(parsed.content, data);
|
|
7056
7628
|
}
|
|
7057
7629
|
|
|
7058
7630
|
// src/commands/owners.ts
|
|
@@ -7131,7 +7703,19 @@ async function chooseGlobs(suggested) {
|
|
|
7131
7703
|
placeholder: "src/payments/**",
|
|
7132
7704
|
validate: (v) => v.trim().length === 0 ? "A glob is required." : void 0
|
|
7133
7705
|
});
|
|
7134
|
-
|
|
7706
|
+
const analysis = analyzeGlob(cwd(), glob);
|
|
7707
|
+
for (const w of analysis.warnings) log2.warn(w);
|
|
7708
|
+
let value = analysis.normalized;
|
|
7709
|
+
if (analysis.suggestion) {
|
|
7710
|
+
const useSuggested = await confirm2({
|
|
7711
|
+
message: `Use ${analysis.suggestion} instead?`,
|
|
7712
|
+
initialValue: true
|
|
7713
|
+
});
|
|
7714
|
+
if (useSuggested) value = analysis.suggestion;
|
|
7715
|
+
} else if (analysis.normalized !== glob.trim()) {
|
|
7716
|
+
log2.info(`Normalized to ${analysis.normalized}`);
|
|
7717
|
+
}
|
|
7718
|
+
chosen.push(value);
|
|
7135
7719
|
addMore = await confirm2({ message: "Add another glob?", initialValue: false });
|
|
7136
7720
|
}
|
|
7137
7721
|
return [...new Set(chosen)];
|
|
@@ -7214,19 +7798,19 @@ ${globs.map((g) => ` - ${g}`).join("\n")}`);
|
|
|
7214
7798
|
}
|
|
7215
7799
|
|
|
7216
7800
|
// src/commands/module-descriptor.ts
|
|
7217
|
-
import { parse as
|
|
7801
|
+
import { parse as parseYaml13, stringify as stringifyYaml5 } from "yaml";
|
|
7218
7802
|
var DESCRIPTOR_PATH2 = "knowledge/module.yml";
|
|
7219
7803
|
function readDescriptor(dir) {
|
|
7220
7804
|
const path5 = join(dir, DESCRIPTOR_PATH2);
|
|
7221
7805
|
if (!exists(path5)) return null;
|
|
7222
7806
|
try {
|
|
7223
|
-
return
|
|
7807
|
+
return parseYaml13(readFile(path5));
|
|
7224
7808
|
} catch {
|
|
7225
7809
|
return null;
|
|
7226
7810
|
}
|
|
7227
7811
|
}
|
|
7228
7812
|
function writeDescriptor(dir, descriptor) {
|
|
7229
|
-
writeFile(join(dir, DESCRIPTOR_PATH2),
|
|
7813
|
+
writeFile(join(dir, DESCRIPTOR_PATH2), stringifyYaml5(descriptor));
|
|
7230
7814
|
}
|
|
7231
7815
|
async function runModuleDescriptor(opts) {
|
|
7232
7816
|
const dir = cwd();
|
|
@@ -7321,7 +7905,7 @@ function printDescriptor(d) {
|
|
|
7321
7905
|
}
|
|
7322
7906
|
|
|
7323
7907
|
// src/commands/modules-map.ts
|
|
7324
|
-
import { parse as
|
|
7908
|
+
import { parse as parseYaml14, stringify as stringifyYaml6 } from "yaml";
|
|
7325
7909
|
|
|
7326
7910
|
// src/templates/registry.ts
|
|
7327
7911
|
var QUALITY = "## Quality checklist";
|
|
@@ -7464,7 +8048,7 @@ ${QUALITY}
|
|
|
7464
8048
|
- [ ] Capabilities describe outcomes, not implementation.
|
|
7465
8049
|
- [ ] Each capability cites evidence or is flagged as an assumption.
|
|
7466
8050
|
`;
|
|
7467
|
-
var
|
|
8051
|
+
var KNOWLEDGE3 = `---
|
|
7468
8052
|
type: current-state
|
|
7469
8053
|
updated_at: YYYY-MM-DD
|
|
7470
8054
|
---
|
|
@@ -8474,7 +9058,7 @@ var KADDO_TEMPLATES = [
|
|
|
8474
9058
|
description: "What is true about the product right now.",
|
|
8475
9059
|
whenToUse: "Created by `kaddo init`; keep it current as the product evolves.",
|
|
8476
9060
|
relatedCommand: "kaddo init",
|
|
8477
|
-
content:
|
|
9061
|
+
content: KNOWLEDGE3
|
|
8478
9062
|
},
|
|
8479
9063
|
// business / product (bootstrap — consolidated, minimal)
|
|
8480
9064
|
{
|
|
@@ -8828,14 +9412,14 @@ function readModulesDescriptor(dir) {
|
|
|
8828
9412
|
const path5 = join(dir, DESCRIPTOR_PATH3);
|
|
8829
9413
|
if (!exists(path5)) return { version: 1, modules: [] };
|
|
8830
9414
|
try {
|
|
8831
|
-
const parsed =
|
|
9415
|
+
const parsed = parseYaml14(readFile(path5));
|
|
8832
9416
|
return { version: parsed.version ?? 1, modules: parsed.modules ?? [] };
|
|
8833
9417
|
} catch {
|
|
8834
9418
|
return { version: 1, modules: [] };
|
|
8835
9419
|
}
|
|
8836
9420
|
}
|
|
8837
9421
|
function writeModulesDescriptor(dir, descriptor) {
|
|
8838
|
-
writeFile(join(dir, DESCRIPTOR_PATH3),
|
|
9422
|
+
writeFile(join(dir, DESCRIPTOR_PATH3), stringifyYaml6(descriptor));
|
|
8839
9423
|
}
|
|
8840
9424
|
function moduleDir(id) {
|
|
8841
9425
|
return `knowledge/tech/modules/${id}`;
|
|
@@ -9125,6 +9709,49 @@ async function runBootstrap(dir = cwd()) {
|
|
|
9125
9709
|
);
|
|
9126
9710
|
}
|
|
9127
9711
|
|
|
9712
|
+
// src/commands/capsule.ts
|
|
9713
|
+
function slug(s) {
|
|
9714
|
+
return s.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "project";
|
|
9715
|
+
}
|
|
9716
|
+
function runCapsuleExport() {
|
|
9717
|
+
const dir = cwd();
|
|
9718
|
+
const config = requireConfig(dir);
|
|
9719
|
+
intro2("kaddo capsule export");
|
|
9720
|
+
const capsule = buildCapsule(dir, config);
|
|
9721
|
+
const name = slug(config.project.name);
|
|
9722
|
+
const mdPath = join(".kaddo", "exports", `${name}.capsule.md`);
|
|
9723
|
+
const jsonPath = join(".kaddo", "exports", `${name}.capsule.json`);
|
|
9724
|
+
writeFile(join(dir, mdPath), renderCapsuleMarkdown(capsule));
|
|
9725
|
+
writeFile(join(dir, jsonPath), serializeCapsuleJson(capsule));
|
|
9726
|
+
log2.success(`Wrote ${mdPath}`);
|
|
9727
|
+
log2.success(`Wrote ${jsonPath}`);
|
|
9728
|
+
log2.info("Refine it with the capsule-agent before sharing \u2014 and never include secrets or source code.");
|
|
9729
|
+
outro2("Knowledge Capsule exported.");
|
|
9730
|
+
}
|
|
9731
|
+
function runCapsuleAdd(srcPath) {
|
|
9732
|
+
const dir = cwd();
|
|
9733
|
+
requireConfig(dir);
|
|
9734
|
+
intro2("kaddo capsule add");
|
|
9735
|
+
if (!srcPath) {
|
|
9736
|
+
console.error("Usage: kaddo capsule add <path-to-capsule.md>");
|
|
9737
|
+
process.exit(1);
|
|
9738
|
+
}
|
|
9739
|
+
const abs = join(dir, srcPath);
|
|
9740
|
+
const source = exists(abs) ? abs : srcPath;
|
|
9741
|
+
if (!exists(source)) {
|
|
9742
|
+
console.error(`Capsule not found: ${srcPath}`);
|
|
9743
|
+
process.exit(1);
|
|
9744
|
+
}
|
|
9745
|
+
const result = addExternalCapsule(dir, source);
|
|
9746
|
+
if (!result.isCapsuleType) {
|
|
9747
|
+
log2.warn("This file is not marked as a knowledge-capsule (front matter `type: knowledge-capsule`).");
|
|
9748
|
+
}
|
|
9749
|
+
log2.success(`Imported capsule "${result.id}" \u2192 ${result.destRel}`);
|
|
9750
|
+
log2.success("Registered in .kaddo/external.yml");
|
|
9751
|
+
log2.info('Run `kaddo context` \u2014 the pack now includes an "External Knowledge" section.');
|
|
9752
|
+
outro2("External capsule registered.");
|
|
9753
|
+
}
|
|
9754
|
+
|
|
9128
9755
|
// src/index.ts
|
|
9129
9756
|
var require2 = createRequire(import.meta.url);
|
|
9130
9757
|
var { version } = require2("../package.json");
|
|
@@ -9142,6 +9769,13 @@ program.command("bootstrap").description("Build the initial knowledge base for a
|
|
|
9142
9769
|
program.command("create [type]").description("Create a work item (feature, bugfix, hotfix, spike, chore). Use --from roadmap to create from a roadmap candidate.").option("--from <source>", "Create from a source artifact (currently: roadmap)").action(async (type, opts) => {
|
|
9143
9770
|
await runCreate(type ?? "", opts);
|
|
9144
9771
|
});
|
|
9772
|
+
var capsuleCmd = program.command("capsule").description("Export this project as a Knowledge Capsule, or import an external one as context");
|
|
9773
|
+
capsuleCmd.command("export").description("Write a Knowledge Capsule about this project to .kaddo/exports/").action(() => {
|
|
9774
|
+
runCapsuleExport();
|
|
9775
|
+
});
|
|
9776
|
+
capsuleCmd.command("add <path>").description("Register an external Knowledge Capsule as project context (.kaddo/external.yml)").action((path5) => {
|
|
9777
|
+
runCapsuleAdd(path5);
|
|
9778
|
+
});
|
|
9145
9779
|
program.command("guard").description("Check if modified code has related artifacts that were not updated").option("--staged", "Check only staged files").option("--no-interactive", "Disable interactive ignore prompts").option("--ci", "CI mode: output JSON, no prompts, non-blocking").option("--json", "Output JSON (alias for --ci)").option("--workspace", "Also check local mapped module repos from .kaddo/modules.yml (opt-in)").action(async (opts) => {
|
|
9146
9780
|
await runGuard(opts);
|
|
9147
9781
|
});
|