@kaddo/cli 3.13.2 → 3.15.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 +328 -26
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -101,6 +101,7 @@ project:
|
|
|
101
101
|
name: "${meta.name}"
|
|
102
102
|
state: ${meta.state}
|
|
103
103
|
structure: ${meta.structure}
|
|
104
|
+
language: ${meta.language}
|
|
104
105
|
domains: []
|
|
105
106
|
team:
|
|
106
107
|
size: ${meta.teamSize}
|
|
@@ -210,11 +211,20 @@ async function runInit() {
|
|
|
210
211
|
],
|
|
211
212
|
initialValue: "monorepo"
|
|
212
213
|
});
|
|
214
|
+
const language = await select2({
|
|
215
|
+
message: "Project knowledge language (the CLI stays in English)",
|
|
216
|
+
options: [
|
|
217
|
+
{ value: "en", label: "English" },
|
|
218
|
+
{ value: "es", label: "Spanish" }
|
|
219
|
+
],
|
|
220
|
+
initialValue: "en"
|
|
221
|
+
});
|
|
213
222
|
const meta = {
|
|
214
223
|
name: projectName.trim(),
|
|
215
224
|
state,
|
|
216
225
|
teamSize,
|
|
217
|
-
structure
|
|
226
|
+
structure,
|
|
227
|
+
language
|
|
218
228
|
};
|
|
219
229
|
const kaddoConfigPath = join(dir, KADDO_DIR, "config.yml");
|
|
220
230
|
writeFile(kaddoConfigPath, buildConfig(meta));
|
|
@@ -510,11 +520,20 @@ import { parse as parseYaml2 } from "yaml";
|
|
|
510
520
|
var PROJECT_STATES = ["new", "pre-ai", "legacy"];
|
|
511
521
|
var TEAM_SIZES = ["indie", "small", "medium", "enterprise"];
|
|
512
522
|
var REPOSITORY_STRUCTURES = ["monorepo", "multirepo"];
|
|
523
|
+
var PROJECT_LANGUAGES = ["en", "es"];
|
|
513
524
|
var DEFAULTS = {
|
|
514
525
|
state: "pre-ai",
|
|
515
526
|
structure: "monorepo",
|
|
516
|
-
teamSize: "indie"
|
|
527
|
+
teamSize: "indie",
|
|
528
|
+
language: "en"
|
|
517
529
|
};
|
|
530
|
+
function languageLabel(lang) {
|
|
531
|
+
return lang === "es" ? "Spanish" : "English";
|
|
532
|
+
}
|
|
533
|
+
function projectLanguage(config) {
|
|
534
|
+
const lang = config.project.language;
|
|
535
|
+
return lang === "es" ? "es" : "en";
|
|
536
|
+
}
|
|
518
537
|
var ConfigError = class extends Error {
|
|
519
538
|
constructor(message) {
|
|
520
539
|
super(message);
|
|
@@ -536,12 +555,18 @@ var structureSchema = z.enum(["monorepo", "multirepo"], {
|
|
|
536
555
|
message: `project.structure must be one of: ${REPOSITORY_STRUCTURES.join(", ")}`
|
|
537
556
|
})
|
|
538
557
|
});
|
|
558
|
+
var languageSchema = z.enum(["en", "es"], {
|
|
559
|
+
errorMap: () => ({
|
|
560
|
+
message: `project.language must be one of: ${PROJECT_LANGUAGES.join(", ")}`
|
|
561
|
+
})
|
|
562
|
+
});
|
|
539
563
|
var configSchema = z.object({
|
|
540
564
|
version: z.union([z.string(), z.number()]).optional(),
|
|
541
565
|
project: z.object({
|
|
542
566
|
name: z.string().default("project"),
|
|
543
567
|
state: projectStateSchema.default(DEFAULTS.state),
|
|
544
568
|
structure: structureSchema.default(DEFAULTS.structure),
|
|
569
|
+
language: languageSchema.default(DEFAULTS.language),
|
|
545
570
|
domains: z.array(z.string()).optional()
|
|
546
571
|
}).passthrough().default({}),
|
|
547
572
|
team: z.object({
|
|
@@ -599,8 +624,8 @@ function createGuidanceForState(state) {
|
|
|
599
624
|
}
|
|
600
625
|
function describeProject(config) {
|
|
601
626
|
const state = config.project.state;
|
|
602
|
-
const
|
|
603
|
-
return `${
|
|
627
|
+
const stateLabel3 = state === "pre-ai" ? "pre-AI" : state;
|
|
628
|
+
return `${stateLabel3} ${config.project.structure}`;
|
|
604
629
|
}
|
|
605
630
|
|
|
606
631
|
// src/core/command-help.ts
|
|
@@ -1586,6 +1611,19 @@ var RESPONSIBILITY_MATRIX = {
|
|
|
1586
1611
|
],
|
|
1587
1612
|
next: ["kaddo scan", "kaddo owners suggest", "kaddo guard", "kaddo explain"]
|
|
1588
1613
|
},
|
|
1614
|
+
"ownership-agent": {
|
|
1615
|
+
agent: "ownership-agent",
|
|
1616
|
+
responsibleFor: ["Precise code: ownership for Work Items and artifacts"],
|
|
1617
|
+
produces: ["proposed code: globs"],
|
|
1618
|
+
canSuggest: ["kaddo owners suggest", "kaddo guard"],
|
|
1619
|
+
cannotSuggest: [
|
|
1620
|
+
"code",
|
|
1621
|
+
"branches",
|
|
1622
|
+
"commits",
|
|
1623
|
+
"modifying files without confirmation"
|
|
1624
|
+
],
|
|
1625
|
+
next: ["kaddo owners suggest", "kaddo guard"]
|
|
1626
|
+
},
|
|
1589
1627
|
"guard-agent": {
|
|
1590
1628
|
agent: "guard-agent",
|
|
1591
1629
|
responsibleFor: ["Knowledge drift"],
|
|
@@ -1654,11 +1692,24 @@ function renderAgentTrace(agent) {
|
|
|
1654
1692
|
""
|
|
1655
1693
|
].join("\n");
|
|
1656
1694
|
}
|
|
1695
|
+
function renderLanguageRule() {
|
|
1696
|
+
return [
|
|
1697
|
+
"## Project Language",
|
|
1698
|
+
"",
|
|
1699
|
+
"The project knowledge language is defined in `.kaddo/config.yml` (`project.language`) and shown",
|
|
1700
|
+
"in the context pack's Project Metadata (`Language:`). Write **all** generated knowledge",
|
|
1701
|
+
"artifacts in that language (default: English).",
|
|
1702
|
+
"",
|
|
1703
|
+
"Do not translate: code, file names, CLI commands or configuration keys.",
|
|
1704
|
+
""
|
|
1705
|
+
].join("\n");
|
|
1706
|
+
}
|
|
1657
1707
|
function withResponsibilityTrace(fileName, content) {
|
|
1658
1708
|
const agent = fileName.replace(/\.md$/, "");
|
|
1659
1709
|
if (!RESPONSIBILITY_MATRIX[agent]) return content;
|
|
1660
1710
|
return `${content.trimEnd()}
|
|
1661
1711
|
|
|
1712
|
+
${renderLanguageRule()}
|
|
1662
1713
|
${renderAgentBoundaries(agent)}
|
|
1663
1714
|
${renderAgentTrace(agent)}`;
|
|
1664
1715
|
}
|
|
@@ -1846,13 +1897,15 @@ Generated from Kaddo Context Pack.
|
|
|
1846
1897
|
|
|
1847
1898
|
Save the architecture overview as \`knowledge/tech/current-state.md\`, supporting notes as
|
|
1848
1899
|
\`knowledge/tech/architecture-notes.md\`, and decision candidates as
|
|
1849
|
-
\`knowledge/tech/decision-candidates.md\`.
|
|
1900
|
+
\`knowledge/tech/decision-candidates.md\`. Final ADRs always live under
|
|
1901
|
+
\`knowledge/tech/decisions/\` \u2014 never directly in \`knowledge/tech/\`.
|
|
1850
1902
|
|
|
1851
1903
|
## Quality Checklist
|
|
1852
1904
|
|
|
1853
1905
|
- Every component is backed by evidence from the context pack.
|
|
1854
1906
|
- Assumptions and confidence are explicit.
|
|
1855
1907
|
- No final decisions are asserted \u2014 only candidates.
|
|
1908
|
+
- Final ADRs go to \`knowledge/tech/decisions/\`, not \`knowledge/tech/\`.
|
|
1856
1909
|
- Open questions are listed.
|
|
1857
1910
|
`;
|
|
1858
1911
|
var ROADMAP_AGENT = `# Roadmap Agent
|
|
@@ -2171,12 +2224,16 @@ Generated from Kaddo Context Pack.
|
|
|
2171
2224
|
|
|
2172
2225
|
## Where to Save the Result
|
|
2173
2226
|
|
|
2174
|
-
Save
|
|
2227
|
+
Save decision **candidates** as \`knowledge/tech/decision-candidates.md\`. When a candidate becomes
|
|
2228
|
+
a **final ADR**, it must live under \`knowledge/tech/decisions/\` (one file per decision, e.g.
|
|
2229
|
+
\`knowledge/tech/decisions/ADR-0001-<slug>.md\`) \u2014 **never** directly in \`knowledge/tech/\`.
|
|
2230
|
+
(Decision = the concept \xB7 ADR = the format \xB7 Path = \`knowledge/tech/decisions/\`.)
|
|
2175
2231
|
|
|
2176
2232
|
## Quality Checklist
|
|
2177
2233
|
|
|
2178
2234
|
- Each candidate has context and alternatives.
|
|
2179
2235
|
- No decision is asserted as final.
|
|
2236
|
+
- Final ADRs go to \`knowledge/tech/decisions/\`, never to \`knowledge/tech/\` directly.
|
|
2180
2237
|
- Assumptions are marked.
|
|
2181
2238
|
- Validation needs are explicit.
|
|
2182
2239
|
`;
|
|
@@ -2875,6 +2932,69 @@ Code, tests and migrations live in the repository. Knowledge updates go under \`
|
|
|
2875
2932
|
- Affected knowledge is updated.
|
|
2876
2933
|
- Commit is suggested and awaits human confirmation \u2014 never run automatically.
|
|
2877
2934
|
`;
|
|
2935
|
+
var OWNERSHIP_AGENT = `# Ownership Agent
|
|
2936
|
+
|
|
2937
|
+
## Role
|
|
2938
|
+
|
|
2939
|
+
You are the Kaddo Ownership Agent. Your job is to propose **precise** \`code:\` ownership globs for
|
|
2940
|
+
Work Items and knowledge artifacts, so Guard can relate code changes to the right knowledge.
|
|
2941
|
+
|
|
2942
|
+
You do not write code, you do not modify files, and you never run Git. You propose; the human
|
|
2943
|
+
confirms and applies (with \`kaddo owners suggest\`).
|
|
2944
|
+
|
|
2945
|
+
## When to Use
|
|
2946
|
+
|
|
2947
|
+
Use this agent after \`kaddo scan\` and \`kaddo context\`, when Work Items or artifacts are missing
|
|
2948
|
+
\`code:\` ownership, or when existing ownership is too broad or inaccurate.
|
|
2949
|
+
|
|
2950
|
+
## Input Required
|
|
2951
|
+
|
|
2952
|
+
Provide \`.kaddo/context-pack.md\` as the primary input, plus the Work Items under
|
|
2953
|
+
\`knowledge/delivery/work-items/\`, \`knowledge/tech/codebase.md\` and \`knowledge/inventory.md\` when
|
|
2954
|
+
they exist (for the real source structure).
|
|
2955
|
+
|
|
2956
|
+
## Expected Output
|
|
2957
|
+
|
|
2958
|
+
For each artifact, a precise set of \`code:\` globs.
|
|
2959
|
+
|
|
2960
|
+
## Instructions
|
|
2961
|
+
|
|
2962
|
+
1. Map each Work Item / artifact to the smallest set of paths that actually implement it.
|
|
2963
|
+
2. Prefer **narrow** globs (e.g. \`src/payments/**\`) over broad ones (e.g. \`src/**\`).
|
|
2964
|
+
3. Use real paths from the inventory/codebase \u2014 do not invent directories.
|
|
2965
|
+
4. Include relevant root files (e.g. \`package.json\`, \`tsconfig.json\`) when they belong.
|
|
2966
|
+
5. Flag artifacts where ownership is genuinely unclear instead of guessing broadly.
|
|
2967
|
+
|
|
2968
|
+
## Constraints
|
|
2969
|
+
|
|
2970
|
+
- Do not implement code.
|
|
2971
|
+
- Do not modify files without confirmation \u2014 propose globs for the human to apply.
|
|
2972
|
+
- Do not create branches or commits; never run Git.
|
|
2973
|
+
- Prefer precision: broad globs reduce Guard usefulness.
|
|
2974
|
+
|
|
2975
|
+
## Output Format
|
|
2976
|
+
|
|
2977
|
+
\`\`\`yaml
|
|
2978
|
+
# <Work Item id> \u2014 proposed ownership
|
|
2979
|
+
code:
|
|
2980
|
+
- package.json
|
|
2981
|
+
- tsconfig.json
|
|
2982
|
+
- src/cli/**
|
|
2983
|
+
- src/shared/**
|
|
2984
|
+
\`\`\`
|
|
2985
|
+
|
|
2986
|
+
## Where to Save the Result
|
|
2987
|
+
|
|
2988
|
+
The human applies the proposed globs to the artifact's front matter with \`kaddo owners suggest\`
|
|
2989
|
+
(or by editing the \`code:\` field). This agent does not write files.
|
|
2990
|
+
|
|
2991
|
+
## Quality Checklist
|
|
2992
|
+
|
|
2993
|
+
- Globs are narrow and based on real paths.
|
|
2994
|
+
- No \`src/**\`-style catch-alls unless truly justified.
|
|
2995
|
+
- Unclear ownership is flagged, not guessed.
|
|
2996
|
+
- Output is a proposal for human confirmation \u2014 nothing is applied automatically.
|
|
2997
|
+
`;
|
|
2878
2998
|
var BACKLOG_AGENT = `# Backlog Agent
|
|
2879
2999
|
|
|
2880
3000
|
## Role
|
|
@@ -2986,7 +3106,9 @@ var AGENT_PROMPTS = [
|
|
|
2986
3106
|
// Implementation (the only agent that may suggest a branch — VS-044)
|
|
2987
3107
|
{ fileName: "implementation-agent.md", content: IMPLEMENTATION_AGENT },
|
|
2988
3108
|
// Backlog capture (idea → draft / roadmap candidate — VS-050)
|
|
2989
|
-
{ fileName: "backlog-agent.md", content: BACKLOG_AGENT }
|
|
3109
|
+
{ fileName: "backlog-agent.md", content: BACKLOG_AGENT },
|
|
3110
|
+
// Ownership proposals (precise code: globs — VS-052)
|
|
3111
|
+
{ fileName: "ownership-agent.md", content: OWNERSHIP_AGENT }
|
|
2990
3112
|
// Every official prompt ends with its responsibility boundaries + Agent Trace footer.
|
|
2991
3113
|
].map((p2) => ({ fileName: p2.fileName, content: withResponsibilityTrace(p2.fileName, p2.content) }));
|
|
2992
3114
|
|
|
@@ -3008,6 +3130,7 @@ var AGENT_GROUPS = {
|
|
|
3008
3130
|
"roadmap-agent.md",
|
|
3009
3131
|
"work-item-agent.md",
|
|
3010
3132
|
"implementation-agent.md",
|
|
3133
|
+
"ownership-agent.md",
|
|
3011
3134
|
"git-strategy-agent.md"
|
|
3012
3135
|
],
|
|
3013
3136
|
utilities: ["legacy-agent.md"]
|
|
@@ -3041,7 +3164,8 @@ var RECOMMENDED_BY_STATE = {
|
|
|
3041
3164
|
"roadmap-agent.md",
|
|
3042
3165
|
"backlog-agent.md",
|
|
3043
3166
|
"work-item-agent.md",
|
|
3044
|
-
"implementation-agent.md"
|
|
3167
|
+
"implementation-agent.md",
|
|
3168
|
+
"ownership-agent.md"
|
|
3045
3169
|
],
|
|
3046
3170
|
legacy: [
|
|
3047
3171
|
"legacy-agent.md",
|
|
@@ -3050,7 +3174,8 @@ var RECOMMENDED_BY_STATE = {
|
|
|
3050
3174
|
"roadmap-agent.md",
|
|
3051
3175
|
"backlog-agent.md",
|
|
3052
3176
|
"work-item-agent.md",
|
|
3053
|
-
"implementation-agent.md"
|
|
3177
|
+
"implementation-agent.md",
|
|
3178
|
+
"ownership-agent.md"
|
|
3054
3179
|
]
|
|
3055
3180
|
};
|
|
3056
3181
|
function recommendedAgents(state) {
|
|
@@ -4196,6 +4321,14 @@ async function isGitRepo(dir) {
|
|
|
4196
4321
|
return false;
|
|
4197
4322
|
}
|
|
4198
4323
|
}
|
|
4324
|
+
async function getUntrackedFiles() {
|
|
4325
|
+
try {
|
|
4326
|
+
const result = await execa("git", ["ls-files", "--others", "--exclude-standard"]);
|
|
4327
|
+
return result.stdout.split("\n").map((f) => f.trim()).filter(Boolean);
|
|
4328
|
+
} catch {
|
|
4329
|
+
return [];
|
|
4330
|
+
}
|
|
4331
|
+
}
|
|
4199
4332
|
|
|
4200
4333
|
// src/services/artifact-reader.ts
|
|
4201
4334
|
import matter from "gray-matter";
|
|
@@ -4216,7 +4349,8 @@ function parseArtifact(filePath, raw) {
|
|
|
4216
4349
|
status: String(data.status ?? ""),
|
|
4217
4350
|
phase: String(data.phase ?? ""),
|
|
4218
4351
|
initiative: String(data.initiative ?? data.source_initiative ?? ""),
|
|
4219
|
-
source: data.source ? String(data.source) : ""
|
|
4352
|
+
source: data.source ? String(data.source) : "",
|
|
4353
|
+
sourceId: String(data.source_id ?? "")
|
|
4220
4354
|
};
|
|
4221
4355
|
} catch {
|
|
4222
4356
|
return null;
|
|
@@ -4854,6 +4988,15 @@ async function runGuard(opts = {}) {
|
|
|
4854
4988
|
const plugins = resolvePlugins(config.plugins ?? []);
|
|
4855
4989
|
const mode = opts.staged ? "staged" : "head";
|
|
4856
4990
|
const currentFiles = await getModifiedFiles(mode);
|
|
4991
|
+
const untrackedFiles = opts.staged ? [] : await getUntrackedFiles();
|
|
4992
|
+
const printUntracked = () => {
|
|
4993
|
+
if (untrackedFiles.length === 0 || jsonMode) return;
|
|
4994
|
+
console.log("");
|
|
4995
|
+
console.log(`Untracked files detected (${untrackedFiles.length}):`);
|
|
4996
|
+
for (const f of untrackedFiles.slice(0, 10)) console.log(` - ${f}`);
|
|
4997
|
+
if (untrackedFiles.length > 10) console.log(` \u2026and ${untrackedFiles.length - 10} more`);
|
|
4998
|
+
console.log("Guard may not fully evaluate these files until they are tracked. (FYI \u2014 non-blocking)");
|
|
4999
|
+
};
|
|
4857
5000
|
let workspaceScan = null;
|
|
4858
5001
|
if (opts.workspace) {
|
|
4859
5002
|
workspaceScan = await collectWorkspaceChanges(dir, mode);
|
|
@@ -4863,6 +5006,7 @@ async function runGuard(opts = {}) {
|
|
|
4863
5006
|
if (touchedFiles.length === 0) {
|
|
4864
5007
|
if (workspaceScan) printWorkspaceHeader(workspaceScan);
|
|
4865
5008
|
console.log("kaddo guard: no modified files detected.");
|
|
5009
|
+
printUntracked();
|
|
4866
5010
|
return;
|
|
4867
5011
|
}
|
|
4868
5012
|
const archDir = join(dir, ARCH_DIR3);
|
|
@@ -4902,10 +5046,12 @@ async function runGuard(opts = {}) {
|
|
|
4902
5046
|
if (workspaceScan) printWorkspaceHeader(workspaceScan);
|
|
4903
5047
|
printHeader(touchedFiles);
|
|
4904
5048
|
console.log(" No artifact ownership matches found.");
|
|
5049
|
+
printUntracked();
|
|
4905
5050
|
return;
|
|
4906
5051
|
}
|
|
4907
5052
|
if (workspaceScan) printWorkspaceHeader(workspaceScan);
|
|
4908
5053
|
printHeader(touchedFiles);
|
|
5054
|
+
printUntracked();
|
|
4909
5055
|
for (const match of activeMatches) {
|
|
4910
5056
|
printFYI(match, dir);
|
|
4911
5057
|
}
|
|
@@ -5031,22 +5177,30 @@ function assessPhase(input) {
|
|
|
5031
5177
|
const firstOf = (s) => items.find((i) => i.lifecycle === s);
|
|
5032
5178
|
let recommendedAgents2 = [];
|
|
5033
5179
|
let nextStep = "";
|
|
5180
|
+
let llmInstructions = [];
|
|
5034
5181
|
switch (phase) {
|
|
5035
5182
|
case "Discovery": {
|
|
5036
5183
|
const m = firstMissingLayerAgent(input.layers);
|
|
5037
5184
|
recommendedAgents2 = [m.agent];
|
|
5038
5185
|
nextStep = m.step;
|
|
5186
|
+
llmInstructions = [`Use the ${m.agent} to fill the missing base knowledge.`, "Do not write code."];
|
|
5039
5187
|
break;
|
|
5040
5188
|
}
|
|
5041
5189
|
case "Planning": {
|
|
5042
5190
|
recommendedAgents2 = ["roadmap-agent"];
|
|
5043
5191
|
nextStep = "Use roadmap-agent to create knowledge/delivery/roadmap.md";
|
|
5192
|
+
llmInstructions = ["Use the roadmap-agent.", "Do not write code.", "Generate roadmap candidates."];
|
|
5044
5193
|
break;
|
|
5045
5194
|
}
|
|
5046
5195
|
case "Delivery Preparation": {
|
|
5047
5196
|
recommendedAgents2 = ["kaddo create --from roadmap", "work-item-agent"];
|
|
5048
5197
|
const n = input.roadmap.remaining || input.roadmap.candidates;
|
|
5049
5198
|
nextStep = `Run \`kaddo create --from roadmap\`${n ? ` (${n} candidate(s))` : ""}, then refine with work-item-agent`;
|
|
5199
|
+
llmInstructions = [
|
|
5200
|
+
"Materialize roadmap candidates with `kaddo create --from roadmap`.",
|
|
5201
|
+
"Use the work-item-agent to refine them.",
|
|
5202
|
+
"Do not implement yet."
|
|
5203
|
+
];
|
|
5050
5204
|
break;
|
|
5051
5205
|
}
|
|
5052
5206
|
case "Active Delivery": {
|
|
@@ -5054,21 +5208,41 @@ function assessPhase(input) {
|
|
|
5054
5208
|
const wi = firstOf("ready");
|
|
5055
5209
|
recommendedAgents2 = ["implementation-agent"];
|
|
5056
5210
|
nextStep = wi ? `Start ${wi.id} \u2014 ${wi.title} (ready \u2192 in-progress)` : "Start a ready Work Item";
|
|
5211
|
+
llmInstructions = [
|
|
5212
|
+
"Use the implementation-agent.",
|
|
5213
|
+
"Suggest a branch name only.",
|
|
5214
|
+
"Do not run git commands."
|
|
5215
|
+
];
|
|
5057
5216
|
} else if (bs["in-progress"] > 0) {
|
|
5058
5217
|
const wi = firstOf("in-progress");
|
|
5059
5218
|
recommendedAgents2 = ["implementation-agent", "kaddo scan", "kaddo owners suggest", "kaddo guard"];
|
|
5060
5219
|
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";
|
|
5220
|
+
llmInstructions = [
|
|
5221
|
+
"Continue the in-progress Work Item with the implementation-agent.",
|
|
5222
|
+
"After changes, run `kaddo scan`, `kaddo owners suggest` and `kaddo guard`.",
|
|
5223
|
+
"Do not commit, push or merge without explicit human confirmation."
|
|
5224
|
+
];
|
|
5061
5225
|
} else if (bs.draft > 0) {
|
|
5062
5226
|
const wi = firstOf("draft");
|
|
5063
5227
|
recommendedAgents2 = ["work-item-agent"];
|
|
5064
5228
|
nextStep = wi ? `Refine ${wi.id} from draft to ready` : "Refine a draft Work Item to ready";
|
|
5229
|
+
llmInstructions = [
|
|
5230
|
+
"Refine draft Work Items to ready.",
|
|
5231
|
+
"Use the work-item-agent.",
|
|
5232
|
+
"Do not implement unless the user explicitly asks."
|
|
5233
|
+
];
|
|
5065
5234
|
} else if (bs.blocked > 0) {
|
|
5066
5235
|
const wi = firstOf("blocked");
|
|
5067
5236
|
recommendedAgents2 = ["work-item-agent"];
|
|
5068
5237
|
nextStep = wi ? `Resolve the blocker on ${wi.id} \u2014 ${wi.title}` : "Resolve the blockers on active work";
|
|
5238
|
+
llmInstructions = [
|
|
5239
|
+
"Resolve the blockers with the work-item-agent.",
|
|
5240
|
+
"Do not implement blocked work."
|
|
5241
|
+
];
|
|
5069
5242
|
}
|
|
5070
5243
|
if (input.ownership.workItemsMissingOwnership > 0) {
|
|
5071
5244
|
recommendedAgents2.push("kaddo owners suggest");
|
|
5245
|
+
llmInstructions.push("Ownership is incomplete \u2014 propose `code:` globs (run `kaddo owners suggest`).");
|
|
5072
5246
|
}
|
|
5073
5247
|
break;
|
|
5074
5248
|
}
|
|
@@ -5076,14 +5250,16 @@ function assessPhase(input) {
|
|
|
5076
5250
|
if (input.roadmap.remaining > 0) {
|
|
5077
5251
|
recommendedAgents2 = ["kaddo create --from roadmap", "work-item-agent"];
|
|
5078
5252
|
nextStep = `Materialize ${input.roadmap.remaining} remaining roadmap candidate(s)`;
|
|
5253
|
+
llmInstructions = ["Materialize the remaining roadmap candidates.", "Do not implement yet."];
|
|
5079
5254
|
} else {
|
|
5080
5255
|
recommendedAgents2 = ["roadmap-agent"];
|
|
5081
5256
|
nextStep = "No active work \u2014 use roadmap-agent to plan the next initiative";
|
|
5257
|
+
llmInstructions = ["No active work.", "Use the roadmap-agent to plan the next initiative."];
|
|
5082
5258
|
}
|
|
5083
5259
|
break;
|
|
5084
5260
|
}
|
|
5085
5261
|
}
|
|
5086
|
-
return { phase, reasons, recommendedAgents: recommendedAgents2, nextStep };
|
|
5262
|
+
return { phase, reasons, recommendedAgents: recommendedAgents2, nextStep, llmInstructions };
|
|
5087
5263
|
}
|
|
5088
5264
|
|
|
5089
5265
|
// src/core/knowledge-discovery.ts
|
|
@@ -5220,6 +5396,30 @@ function renderLayersMarkdown(layers) {
|
|
|
5220
5396
|
|
|
5221
5397
|
// src/core/project-explain.ts
|
|
5222
5398
|
var ARCH_DIR4 = "knowledge";
|
|
5399
|
+
function normalizeTitle(t) {
|
|
5400
|
+
return t.toLowerCase().normalize("NFD").replace(/[̀-ͯ]/g, "").replace(/[^a-z0-9]+/g, " ").trim();
|
|
5401
|
+
}
|
|
5402
|
+
function findDuplicateWorkItems(items) {
|
|
5403
|
+
const groups = [];
|
|
5404
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5405
|
+
const bucket = (key, reason, pred) => {
|
|
5406
|
+
const matched = items.filter(pred);
|
|
5407
|
+
if (matched.length > 1) {
|
|
5408
|
+
const id = `${reason}:${key}:${matched.map((m) => m.id).sort().join(",")}`;
|
|
5409
|
+
if (!seen.has(id)) {
|
|
5410
|
+
seen.add(id);
|
|
5411
|
+
groups.push({ reason, items: matched.map((m) => ({ id: m.id, title: m.title })) });
|
|
5412
|
+
}
|
|
5413
|
+
}
|
|
5414
|
+
};
|
|
5415
|
+
for (const sid of new Set(items.map((i) => i.sourceId).filter(Boolean))) {
|
|
5416
|
+
bucket(sid, `same source candidate (${sid})`, (i) => i.sourceId === sid);
|
|
5417
|
+
}
|
|
5418
|
+
for (const nt of new Set(items.map((i) => normalizeTitle(i.title)).filter(Boolean))) {
|
|
5419
|
+
bucket(nt, "same normalized title", (i) => normalizeTitle(i.title) === nt);
|
|
5420
|
+
}
|
|
5421
|
+
return groups;
|
|
5422
|
+
}
|
|
5223
5423
|
function first(values) {
|
|
5224
5424
|
if (Array.isArray(values)) {
|
|
5225
5425
|
const v = values.find((x) => typeof x === "string" && x);
|
|
@@ -5271,7 +5471,8 @@ function buildProjectExplanation(dir) {
|
|
|
5271
5471
|
name: config?.project.name ?? "unknown",
|
|
5272
5472
|
state: config?.project.state ?? "unknown",
|
|
5273
5473
|
teamSize: config?.team.size ?? "unknown",
|
|
5274
|
-
structure: config?.project.structure ?? "unknown"
|
|
5474
|
+
structure: config?.project.structure ?? "unknown",
|
|
5475
|
+
language: config ? languageLabel(projectLanguage(config)) : "English"
|
|
5275
5476
|
};
|
|
5276
5477
|
const scan2 = loadScan(dir);
|
|
5277
5478
|
const stack = scan2 ? {
|
|
@@ -5337,6 +5538,9 @@ function buildProjectExplanation(dir) {
|
|
|
5337
5538
|
workItemsMissingOwnership: items.length - withOwnership
|
|
5338
5539
|
};
|
|
5339
5540
|
const domains = [...new Set(workItemArtifacts.flatMap((a) => a.domains))].filter(Boolean);
|
|
5541
|
+
const duplicateWorkItems = findDuplicateWorkItems(
|
|
5542
|
+
workItemArtifacts.map((a) => ({ id: a.id || a.title, title: a.title, sourceId: a.sourceId }))
|
|
5543
|
+
);
|
|
5340
5544
|
const roadmapPath = join(dir, ARCH_DIR4, "delivery", "roadmap.md");
|
|
5341
5545
|
const roadmapMd = exists(roadmapPath) ? readFile(roadmapPath) : null;
|
|
5342
5546
|
const roadmap = roadmapStats(roadmapMd, items.length);
|
|
@@ -5386,6 +5590,7 @@ function buildProjectExplanation(dir) {
|
|
|
5386
5590
|
workItems,
|
|
5387
5591
|
ownership,
|
|
5388
5592
|
domains,
|
|
5593
|
+
duplicateWorkItems,
|
|
5389
5594
|
layers,
|
|
5390
5595
|
roadmap,
|
|
5391
5596
|
mappedModules,
|
|
@@ -5417,6 +5622,7 @@ function renderExplanationHuman(exp) {
|
|
|
5417
5622
|
lines.push(`- State: ${stateLabel(exp.project.state)}`);
|
|
5418
5623
|
lines.push(`- Team: ${exp.project.teamSize}`);
|
|
5419
5624
|
lines.push(`- Structure: ${exp.project.structure}`);
|
|
5625
|
+
lines.push(`- Project language: ${exp.project.language}`);
|
|
5420
5626
|
lines.push("");
|
|
5421
5627
|
lines.push("## Knowledge Layers");
|
|
5422
5628
|
lines.push(renderLayersMarkdown(exp.layers));
|
|
@@ -5518,6 +5724,15 @@ function renderExplanationHuman(exp) {
|
|
|
5518
5724
|
for (const m of exp.missingKnowledge) lines.push(`- ${m}`);
|
|
5519
5725
|
lines.push("");
|
|
5520
5726
|
}
|
|
5727
|
+
if (exp.duplicateWorkItems.length > 0) {
|
|
5728
|
+
lines.push("## Possible Duplicate Work Items");
|
|
5729
|
+
for (const g of exp.duplicateWorkItems) {
|
|
5730
|
+
lines.push(`- ${g.reason}:`);
|
|
5731
|
+
for (const i of g.items) lines.push(` - ${i.id} \u2014 ${i.title}`);
|
|
5732
|
+
}
|
|
5733
|
+
lines.push("Review before continuing (non-blocking).");
|
|
5734
|
+
lines.push("");
|
|
5735
|
+
}
|
|
5521
5736
|
const assessment = assessPhase(exp);
|
|
5522
5737
|
lines.push("## Phase");
|
|
5523
5738
|
lines.push(`- Phase: ${assessment.phase}`);
|
|
@@ -5886,7 +6101,8 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
|
|
|
5886
6101
|
name: config.project.name,
|
|
5887
6102
|
state,
|
|
5888
6103
|
teamSize: config.team.size,
|
|
5889
|
-
structure: config.project.structure
|
|
6104
|
+
structure: config.project.structure,
|
|
6105
|
+
language: languageLabel(projectLanguage(config))
|
|
5890
6106
|
},
|
|
5891
6107
|
scan: {
|
|
5892
6108
|
available: scanJson !== null,
|
|
@@ -5911,10 +6127,12 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
|
|
|
5911
6127
|
deliveryMix,
|
|
5912
6128
|
mappedModules,
|
|
5913
6129
|
missing,
|
|
6130
|
+
// VS-052: the handoff is driven by the REAL phase, not project.state, so the pack never
|
|
6131
|
+
// contradicts the Current Phase block above it.
|
|
5914
6132
|
handoff: {
|
|
5915
|
-
recommendedAgents: recommendedAgentsForState(state),
|
|
5916
|
-
nextSteps: nextStepsForState2(state),
|
|
5917
|
-
instructions: LLM_INSTRUCTIONS,
|
|
6133
|
+
recommendedAgents: phase.recommendedAgents.length > 0 ? phase.recommendedAgents : recommendedAgentsForState(state),
|
|
6134
|
+
nextSteps: phase.nextStep ? [phase.nextStep] : nextStepsForState2(state),
|
|
6135
|
+
instructions: phase.llmInstructions.length > 0 ? phase.llmInstructions : LLM_INSTRUCTIONS,
|
|
5918
6136
|
operatingRules: OPERATING_RULES
|
|
5919
6137
|
}
|
|
5920
6138
|
};
|
|
@@ -5924,9 +6142,6 @@ function serializeContextPackJson(pack) {
|
|
|
5924
6142
|
}
|
|
5925
6143
|
|
|
5926
6144
|
// src/templates/context-pack-template.ts
|
|
5927
|
-
function stateLabel2(state) {
|
|
5928
|
-
return state === "pre-ai" ? "pre-AI" : state;
|
|
5929
|
-
}
|
|
5930
6145
|
function renderContextPack(pack) {
|
|
5931
6146
|
const { project, scan: scan2, knowledge, missing, handoff } = pack;
|
|
5932
6147
|
const parts = [];
|
|
@@ -5942,7 +6157,8 @@ function renderContextPack(pack) {
|
|
|
5942
6157
|
`- Name: ${project.name}`,
|
|
5943
6158
|
`- State: ${project.state}`,
|
|
5944
6159
|
`- Team size: ${project.teamSize}`,
|
|
5945
|
-
`- Structure: ${project.structure}
|
|
6160
|
+
`- Structure: ${project.structure}`,
|
|
6161
|
+
`- Language: ${project.language}`
|
|
5946
6162
|
].join("\n") + "\n"
|
|
5947
6163
|
);
|
|
5948
6164
|
parts.push("## Current Phase\n");
|
|
@@ -6066,11 +6282,11 @@ function renderContextPack(pack) {
|
|
|
6066
6282
|
parts.push("_None \u2014 all expected context is present._\n");
|
|
6067
6283
|
}
|
|
6068
6284
|
parts.push("## Recommended Agent Handoff\n");
|
|
6069
|
-
parts.push(`Recommended
|
|
6285
|
+
parts.push(`Recommended next for the **${pack.phase.phase}** phase:
|
|
6070
6286
|
`);
|
|
6071
6287
|
parts.push(handoff.recommendedAgents.map((a, i) => `${i + 1}. ${a}`).join("\n") + "\n");
|
|
6072
6288
|
if (handoff.nextSteps.length > 0) {
|
|
6073
|
-
parts.push("Next
|
|
6289
|
+
parts.push("Next step:\n");
|
|
6074
6290
|
parts.push(handoff.nextSteps.map((s) => `- ${s}`).join("\n") + "\n");
|
|
6075
6291
|
}
|
|
6076
6292
|
parts.push("## Instructions for the LLM\n");
|
|
@@ -6169,7 +6385,7 @@ function buildUnderstandPlan(dir, config) {
|
|
|
6169
6385
|
}
|
|
6170
6386
|
|
|
6171
6387
|
// src/templates/understand-template.ts
|
|
6172
|
-
function
|
|
6388
|
+
function stateLabel2(state) {
|
|
6173
6389
|
return state === "pre-ai" ? "pre-AI" : state;
|
|
6174
6390
|
}
|
|
6175
6391
|
function agentName(file) {
|
|
@@ -6192,7 +6408,7 @@ function renderUnderstand(plan) {
|
|
|
6192
6408
|
].join("\n") + "\n"
|
|
6193
6409
|
);
|
|
6194
6410
|
parts.push("## Recommended Agent Flow\n");
|
|
6195
|
-
parts.push(`Recommended order for a ${
|
|
6411
|
+
parts.push(`Recommended order for a ${stateLabel2(project.state)} project:
|
|
6196
6412
|
`);
|
|
6197
6413
|
parts.push(
|
|
6198
6414
|
steps.map((s, i) => {
|
|
@@ -6365,6 +6581,7 @@ function runUnderstand() {
|
|
|
6365
6581
|
);
|
|
6366
6582
|
}
|
|
6367
6583
|
console.log(renderUnderstandTerminal(plan));
|
|
6584
|
+
console.log(`Project language: ${pack.project.language} (knowledge artifacts are written in this language)`);
|
|
6368
6585
|
const exp = buildProjectExplanation(dir);
|
|
6369
6586
|
const assessment = assessPhase(exp);
|
|
6370
6587
|
console.log("");
|
|
@@ -6943,6 +7160,64 @@ function runAdd(moduleName, opts = {}, dir = cwd()) {
|
|
|
6943
7160
|
// src/core/ownership-suggest.ts
|
|
6944
7161
|
import matter5 from "gray-matter";
|
|
6945
7162
|
var SCAN_PATH = ".kaddo/scan.json";
|
|
7163
|
+
function normalizeGlob(input) {
|
|
7164
|
+
let g = input.trim().replace(/\\/g, "/");
|
|
7165
|
+
if (!g) return g;
|
|
7166
|
+
if (g.endsWith("/")) return `${g.slice(0, -1)}/**`;
|
|
7167
|
+
if (!g.includes("*")) {
|
|
7168
|
+
const base = g.split("/").pop() ?? "";
|
|
7169
|
+
const looksLikeFile = /\.[A-Za-z0-9]+$/.test(base);
|
|
7170
|
+
if (!looksLikeFile) return `${g}/**`;
|
|
7171
|
+
}
|
|
7172
|
+
return g;
|
|
7173
|
+
}
|
|
7174
|
+
function levenshtein(a, b) {
|
|
7175
|
+
const m = a.length;
|
|
7176
|
+
const n = b.length;
|
|
7177
|
+
const d = Array.from({ length: m + 1 }, (_, i) => [i, ...Array(n).fill(0)]);
|
|
7178
|
+
for (let j = 0; j <= n; j++) d[0][j] = j;
|
|
7179
|
+
for (let i = 1; i <= m; i++) {
|
|
7180
|
+
for (let j = 1; j <= n; j++) {
|
|
7181
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
7182
|
+
d[i][j] = Math.min(d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1] + cost);
|
|
7183
|
+
}
|
|
7184
|
+
}
|
|
7185
|
+
return d[m][n];
|
|
7186
|
+
}
|
|
7187
|
+
function analyzeGlob(dir, input) {
|
|
7188
|
+
const normalized = normalizeGlob(input);
|
|
7189
|
+
const warnings = [];
|
|
7190
|
+
let suggestion;
|
|
7191
|
+
if (/^\*+$/.test(normalized) || /^[^/*]+\/\*\*$/.test(normalized)) {
|
|
7192
|
+
warnings.push(
|
|
7193
|
+
"This glob is broad and may reduce Guard usefulness. Prefer narrower ownership when possible."
|
|
7194
|
+
);
|
|
7195
|
+
}
|
|
7196
|
+
const starIdx = normalized.indexOf("*");
|
|
7197
|
+
const literal = (starIdx >= 0 ? normalized.slice(0, starIdx) : normalized).replace(/\/+$/, "");
|
|
7198
|
+
if (literal && !exists(join(dir, literal))) {
|
|
7199
|
+
const slash = literal.lastIndexOf("/");
|
|
7200
|
+
const parent = slash >= 0 ? literal.slice(0, slash) : "";
|
|
7201
|
+
const target = slash >= 0 ? literal.slice(slash + 1) : literal;
|
|
7202
|
+
const parentFull = parent ? join(dir, parent) : dir;
|
|
7203
|
+
let near;
|
|
7204
|
+
try {
|
|
7205
|
+
if (exists(parentFull)) {
|
|
7206
|
+
near = readDir(parentFull).find(
|
|
7207
|
+
(e) => e !== target && levenshtein(e.toLowerCase(), target.toLowerCase()) <= 2
|
|
7208
|
+
);
|
|
7209
|
+
}
|
|
7210
|
+
} catch {
|
|
7211
|
+
}
|
|
7212
|
+
if (near) {
|
|
7213
|
+
suggestion = `${parent ? parent + "/" : ""}${near}${normalized.slice(literal.length)}`;
|
|
7214
|
+
warnings.push(`Path does not exist. Did you mean ${suggestion}?`);
|
|
7215
|
+
} else {
|
|
7216
|
+
warnings.push(`Path "${literal}" does not exist in the project.`);
|
|
7217
|
+
}
|
|
7218
|
+
}
|
|
7219
|
+
return { normalized, warnings, suggestion };
|
|
7220
|
+
}
|
|
6946
7221
|
function toStringArray3(value) {
|
|
6947
7222
|
return Array.isArray(value) ? value.map((v) => String(v)).filter(Boolean) : [];
|
|
6948
7223
|
}
|
|
@@ -7088,7 +7363,19 @@ async function chooseGlobs(suggested) {
|
|
|
7088
7363
|
placeholder: "src/payments/**",
|
|
7089
7364
|
validate: (v) => v.trim().length === 0 ? "A glob is required." : void 0
|
|
7090
7365
|
});
|
|
7091
|
-
|
|
7366
|
+
const analysis = analyzeGlob(cwd(), glob);
|
|
7367
|
+
for (const w of analysis.warnings) log2.warn(w);
|
|
7368
|
+
let value = analysis.normalized;
|
|
7369
|
+
if (analysis.suggestion) {
|
|
7370
|
+
const useSuggested = await confirm2({
|
|
7371
|
+
message: `Use ${analysis.suggestion} instead?`,
|
|
7372
|
+
initialValue: true
|
|
7373
|
+
});
|
|
7374
|
+
if (useSuggested) value = analysis.suggestion;
|
|
7375
|
+
} else if (analysis.normalized !== glob.trim()) {
|
|
7376
|
+
log2.info(`Normalized to ${analysis.normalized}`);
|
|
7377
|
+
}
|
|
7378
|
+
chosen.push(value);
|
|
7092
7379
|
addMore = await confirm2({ message: "Add another glob?", initialValue: false });
|
|
7093
7380
|
}
|
|
7094
7381
|
return [...new Set(chosen)];
|
|
@@ -9003,6 +9290,12 @@ var BOOTSTRAP_LAYERS = ["Business", "Product", "Tech"];
|
|
|
9003
9290
|
function bootstrap(dir) {
|
|
9004
9291
|
const written = [];
|
|
9005
9292
|
const skipped = [];
|
|
9293
|
+
let language = "en";
|
|
9294
|
+
try {
|
|
9295
|
+
const config = loadConfig(dir);
|
|
9296
|
+
if (config) language = projectLanguage(config);
|
|
9297
|
+
} catch {
|
|
9298
|
+
}
|
|
9006
9299
|
for (const target of TARGETS) {
|
|
9007
9300
|
const full = join(dir, target.path);
|
|
9008
9301
|
if (exists(full)) {
|
|
@@ -9010,13 +9303,22 @@ function bootstrap(dir) {
|
|
|
9010
9303
|
continue;
|
|
9011
9304
|
}
|
|
9012
9305
|
const tpl = getTemplate(target.templateId);
|
|
9013
|
-
const
|
|
9306
|
+
const base = tpl ? tpl.content : "";
|
|
9307
|
+
const content = withLanguageDirective(base, language);
|
|
9014
9308
|
writeFile(full, content.endsWith("\n") ? content : `${content}
|
|
9015
9309
|
`);
|
|
9016
9310
|
written.push(target.path);
|
|
9017
9311
|
}
|
|
9018
9312
|
return { written, skipped, layers: BOOTSTRAP_LAYERS };
|
|
9019
9313
|
}
|
|
9314
|
+
function withLanguageDirective(content, language) {
|
|
9315
|
+
if (language !== "es") return content;
|
|
9316
|
+
const note = "> Idioma del proyecto: **espa\xF1ol**. Escribe este conocimiento en espa\xF1ol. Mant\xE9n en ingl\xE9s el c\xF3digo, los nombres de archivo, los comandos y las claves de configuraci\xF3n.\n";
|
|
9317
|
+
const fm = content.match(/^---\n[\s\S]*?\n---\n/);
|
|
9318
|
+
if (fm) return content.slice(0, fm[0].length) + "\n" + note + content.slice(fm[0].length);
|
|
9319
|
+
return `${note}
|
|
9320
|
+
${content}`;
|
|
9321
|
+
}
|
|
9020
9322
|
async function runBootstrap(dir = cwd()) {
|
|
9021
9323
|
intro2("kaddo bootstrap");
|
|
9022
9324
|
if (!exists(join(dir, CONFIG_PATH8))) {
|