@kaddo/cli 3.14.0 → 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 +264 -20
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -624,8 +624,8 @@ function createGuidanceForState(state) {
|
|
|
624
624
|
}
|
|
625
625
|
function describeProject(config) {
|
|
626
626
|
const state = config.project.state;
|
|
627
|
-
const
|
|
628
|
-
return `${
|
|
627
|
+
const stateLabel3 = state === "pre-ai" ? "pre-AI" : state;
|
|
628
|
+
return `${stateLabel3} ${config.project.structure}`;
|
|
629
629
|
}
|
|
630
630
|
|
|
631
631
|
// src/core/command-help.ts
|
|
@@ -1611,6 +1611,19 @@ var RESPONSIBILITY_MATRIX = {
|
|
|
1611
1611
|
],
|
|
1612
1612
|
next: ["kaddo scan", "kaddo owners suggest", "kaddo guard", "kaddo explain"]
|
|
1613
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
|
+
},
|
|
1614
1627
|
"guard-agent": {
|
|
1615
1628
|
agent: "guard-agent",
|
|
1616
1629
|
responsibleFor: ["Knowledge drift"],
|
|
@@ -1884,13 +1897,15 @@ Generated from Kaddo Context Pack.
|
|
|
1884
1897
|
|
|
1885
1898
|
Save the architecture overview as \`knowledge/tech/current-state.md\`, supporting notes as
|
|
1886
1899
|
\`knowledge/tech/architecture-notes.md\`, and decision candidates as
|
|
1887
|
-
\`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/\`.
|
|
1888
1902
|
|
|
1889
1903
|
## Quality Checklist
|
|
1890
1904
|
|
|
1891
1905
|
- Every component is backed by evidence from the context pack.
|
|
1892
1906
|
- Assumptions and confidence are explicit.
|
|
1893
1907
|
- No final decisions are asserted \u2014 only candidates.
|
|
1908
|
+
- Final ADRs go to \`knowledge/tech/decisions/\`, not \`knowledge/tech/\`.
|
|
1894
1909
|
- Open questions are listed.
|
|
1895
1910
|
`;
|
|
1896
1911
|
var ROADMAP_AGENT = `# Roadmap Agent
|
|
@@ -2209,12 +2224,16 @@ Generated from Kaddo Context Pack.
|
|
|
2209
2224
|
|
|
2210
2225
|
## Where to Save the Result
|
|
2211
2226
|
|
|
2212
|
-
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/\`.)
|
|
2213
2231
|
|
|
2214
2232
|
## Quality Checklist
|
|
2215
2233
|
|
|
2216
2234
|
- Each candidate has context and alternatives.
|
|
2217
2235
|
- No decision is asserted as final.
|
|
2236
|
+
- Final ADRs go to \`knowledge/tech/decisions/\`, never to \`knowledge/tech/\` directly.
|
|
2218
2237
|
- Assumptions are marked.
|
|
2219
2238
|
- Validation needs are explicit.
|
|
2220
2239
|
`;
|
|
@@ -2913,6 +2932,69 @@ Code, tests and migrations live in the repository. Knowledge updates go under \`
|
|
|
2913
2932
|
- Affected knowledge is updated.
|
|
2914
2933
|
- Commit is suggested and awaits human confirmation \u2014 never run automatically.
|
|
2915
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
|
+
`;
|
|
2916
2998
|
var BACKLOG_AGENT = `# Backlog Agent
|
|
2917
2999
|
|
|
2918
3000
|
## Role
|
|
@@ -3024,7 +3106,9 @@ var AGENT_PROMPTS = [
|
|
|
3024
3106
|
// Implementation (the only agent that may suggest a branch — VS-044)
|
|
3025
3107
|
{ fileName: "implementation-agent.md", content: IMPLEMENTATION_AGENT },
|
|
3026
3108
|
// Backlog capture (idea → draft / roadmap candidate — VS-050)
|
|
3027
|
-
{ 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 }
|
|
3028
3112
|
// Every official prompt ends with its responsibility boundaries + Agent Trace footer.
|
|
3029
3113
|
].map((p2) => ({ fileName: p2.fileName, content: withResponsibilityTrace(p2.fileName, p2.content) }));
|
|
3030
3114
|
|
|
@@ -3046,6 +3130,7 @@ var AGENT_GROUPS = {
|
|
|
3046
3130
|
"roadmap-agent.md",
|
|
3047
3131
|
"work-item-agent.md",
|
|
3048
3132
|
"implementation-agent.md",
|
|
3133
|
+
"ownership-agent.md",
|
|
3049
3134
|
"git-strategy-agent.md"
|
|
3050
3135
|
],
|
|
3051
3136
|
utilities: ["legacy-agent.md"]
|
|
@@ -3079,7 +3164,8 @@ var RECOMMENDED_BY_STATE = {
|
|
|
3079
3164
|
"roadmap-agent.md",
|
|
3080
3165
|
"backlog-agent.md",
|
|
3081
3166
|
"work-item-agent.md",
|
|
3082
|
-
"implementation-agent.md"
|
|
3167
|
+
"implementation-agent.md",
|
|
3168
|
+
"ownership-agent.md"
|
|
3083
3169
|
],
|
|
3084
3170
|
legacy: [
|
|
3085
3171
|
"legacy-agent.md",
|
|
@@ -3088,7 +3174,8 @@ var RECOMMENDED_BY_STATE = {
|
|
|
3088
3174
|
"roadmap-agent.md",
|
|
3089
3175
|
"backlog-agent.md",
|
|
3090
3176
|
"work-item-agent.md",
|
|
3091
|
-
"implementation-agent.md"
|
|
3177
|
+
"implementation-agent.md",
|
|
3178
|
+
"ownership-agent.md"
|
|
3092
3179
|
]
|
|
3093
3180
|
};
|
|
3094
3181
|
function recommendedAgents(state) {
|
|
@@ -4234,6 +4321,14 @@ async function isGitRepo(dir) {
|
|
|
4234
4321
|
return false;
|
|
4235
4322
|
}
|
|
4236
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
|
+
}
|
|
4237
4332
|
|
|
4238
4333
|
// src/services/artifact-reader.ts
|
|
4239
4334
|
import matter from "gray-matter";
|
|
@@ -4254,7 +4349,8 @@ function parseArtifact(filePath, raw) {
|
|
|
4254
4349
|
status: String(data.status ?? ""),
|
|
4255
4350
|
phase: String(data.phase ?? ""),
|
|
4256
4351
|
initiative: String(data.initiative ?? data.source_initiative ?? ""),
|
|
4257
|
-
source: data.source ? String(data.source) : ""
|
|
4352
|
+
source: data.source ? String(data.source) : "",
|
|
4353
|
+
sourceId: String(data.source_id ?? "")
|
|
4258
4354
|
};
|
|
4259
4355
|
} catch {
|
|
4260
4356
|
return null;
|
|
@@ -4892,6 +4988,15 @@ async function runGuard(opts = {}) {
|
|
|
4892
4988
|
const plugins = resolvePlugins(config.plugins ?? []);
|
|
4893
4989
|
const mode = opts.staged ? "staged" : "head";
|
|
4894
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
|
+
};
|
|
4895
5000
|
let workspaceScan = null;
|
|
4896
5001
|
if (opts.workspace) {
|
|
4897
5002
|
workspaceScan = await collectWorkspaceChanges(dir, mode);
|
|
@@ -4901,6 +5006,7 @@ async function runGuard(opts = {}) {
|
|
|
4901
5006
|
if (touchedFiles.length === 0) {
|
|
4902
5007
|
if (workspaceScan) printWorkspaceHeader(workspaceScan);
|
|
4903
5008
|
console.log("kaddo guard: no modified files detected.");
|
|
5009
|
+
printUntracked();
|
|
4904
5010
|
return;
|
|
4905
5011
|
}
|
|
4906
5012
|
const archDir = join(dir, ARCH_DIR3);
|
|
@@ -4940,10 +5046,12 @@ async function runGuard(opts = {}) {
|
|
|
4940
5046
|
if (workspaceScan) printWorkspaceHeader(workspaceScan);
|
|
4941
5047
|
printHeader(touchedFiles);
|
|
4942
5048
|
console.log(" No artifact ownership matches found.");
|
|
5049
|
+
printUntracked();
|
|
4943
5050
|
return;
|
|
4944
5051
|
}
|
|
4945
5052
|
if (workspaceScan) printWorkspaceHeader(workspaceScan);
|
|
4946
5053
|
printHeader(touchedFiles);
|
|
5054
|
+
printUntracked();
|
|
4947
5055
|
for (const match of activeMatches) {
|
|
4948
5056
|
printFYI(match, dir);
|
|
4949
5057
|
}
|
|
@@ -5069,22 +5177,30 @@ function assessPhase(input) {
|
|
|
5069
5177
|
const firstOf = (s) => items.find((i) => i.lifecycle === s);
|
|
5070
5178
|
let recommendedAgents2 = [];
|
|
5071
5179
|
let nextStep = "";
|
|
5180
|
+
let llmInstructions = [];
|
|
5072
5181
|
switch (phase) {
|
|
5073
5182
|
case "Discovery": {
|
|
5074
5183
|
const m = firstMissingLayerAgent(input.layers);
|
|
5075
5184
|
recommendedAgents2 = [m.agent];
|
|
5076
5185
|
nextStep = m.step;
|
|
5186
|
+
llmInstructions = [`Use the ${m.agent} to fill the missing base knowledge.`, "Do not write code."];
|
|
5077
5187
|
break;
|
|
5078
5188
|
}
|
|
5079
5189
|
case "Planning": {
|
|
5080
5190
|
recommendedAgents2 = ["roadmap-agent"];
|
|
5081
5191
|
nextStep = "Use roadmap-agent to create knowledge/delivery/roadmap.md";
|
|
5192
|
+
llmInstructions = ["Use the roadmap-agent.", "Do not write code.", "Generate roadmap candidates."];
|
|
5082
5193
|
break;
|
|
5083
5194
|
}
|
|
5084
5195
|
case "Delivery Preparation": {
|
|
5085
5196
|
recommendedAgents2 = ["kaddo create --from roadmap", "work-item-agent"];
|
|
5086
5197
|
const n = input.roadmap.remaining || input.roadmap.candidates;
|
|
5087
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
|
+
];
|
|
5088
5204
|
break;
|
|
5089
5205
|
}
|
|
5090
5206
|
case "Active Delivery": {
|
|
@@ -5092,21 +5208,41 @@ function assessPhase(input) {
|
|
|
5092
5208
|
const wi = firstOf("ready");
|
|
5093
5209
|
recommendedAgents2 = ["implementation-agent"];
|
|
5094
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
|
+
];
|
|
5095
5216
|
} else if (bs["in-progress"] > 0) {
|
|
5096
5217
|
const wi = firstOf("in-progress");
|
|
5097
5218
|
recommendedAgents2 = ["implementation-agent", "kaddo scan", "kaddo owners suggest", "kaddo guard"];
|
|
5098
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
|
+
];
|
|
5099
5225
|
} else if (bs.draft > 0) {
|
|
5100
5226
|
const wi = firstOf("draft");
|
|
5101
5227
|
recommendedAgents2 = ["work-item-agent"];
|
|
5102
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
|
+
];
|
|
5103
5234
|
} else if (bs.blocked > 0) {
|
|
5104
5235
|
const wi = firstOf("blocked");
|
|
5105
5236
|
recommendedAgents2 = ["work-item-agent"];
|
|
5106
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
|
+
];
|
|
5107
5242
|
}
|
|
5108
5243
|
if (input.ownership.workItemsMissingOwnership > 0) {
|
|
5109
5244
|
recommendedAgents2.push("kaddo owners suggest");
|
|
5245
|
+
llmInstructions.push("Ownership is incomplete \u2014 propose `code:` globs (run `kaddo owners suggest`).");
|
|
5110
5246
|
}
|
|
5111
5247
|
break;
|
|
5112
5248
|
}
|
|
@@ -5114,14 +5250,16 @@ function assessPhase(input) {
|
|
|
5114
5250
|
if (input.roadmap.remaining > 0) {
|
|
5115
5251
|
recommendedAgents2 = ["kaddo create --from roadmap", "work-item-agent"];
|
|
5116
5252
|
nextStep = `Materialize ${input.roadmap.remaining} remaining roadmap candidate(s)`;
|
|
5253
|
+
llmInstructions = ["Materialize the remaining roadmap candidates.", "Do not implement yet."];
|
|
5117
5254
|
} else {
|
|
5118
5255
|
recommendedAgents2 = ["roadmap-agent"];
|
|
5119
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."];
|
|
5120
5258
|
}
|
|
5121
5259
|
break;
|
|
5122
5260
|
}
|
|
5123
5261
|
}
|
|
5124
|
-
return { phase, reasons, recommendedAgents: recommendedAgents2, nextStep };
|
|
5262
|
+
return { phase, reasons, recommendedAgents: recommendedAgents2, nextStep, llmInstructions };
|
|
5125
5263
|
}
|
|
5126
5264
|
|
|
5127
5265
|
// src/core/knowledge-discovery.ts
|
|
@@ -5258,6 +5396,30 @@ function renderLayersMarkdown(layers) {
|
|
|
5258
5396
|
|
|
5259
5397
|
// src/core/project-explain.ts
|
|
5260
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
|
+
}
|
|
5261
5423
|
function first(values) {
|
|
5262
5424
|
if (Array.isArray(values)) {
|
|
5263
5425
|
const v = values.find((x) => typeof x === "string" && x);
|
|
@@ -5376,6 +5538,9 @@ function buildProjectExplanation(dir) {
|
|
|
5376
5538
|
workItemsMissingOwnership: items.length - withOwnership
|
|
5377
5539
|
};
|
|
5378
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
|
+
);
|
|
5379
5544
|
const roadmapPath = join(dir, ARCH_DIR4, "delivery", "roadmap.md");
|
|
5380
5545
|
const roadmapMd = exists(roadmapPath) ? readFile(roadmapPath) : null;
|
|
5381
5546
|
const roadmap = roadmapStats(roadmapMd, items.length);
|
|
@@ -5425,6 +5590,7 @@ function buildProjectExplanation(dir) {
|
|
|
5425
5590
|
workItems,
|
|
5426
5591
|
ownership,
|
|
5427
5592
|
domains,
|
|
5593
|
+
duplicateWorkItems,
|
|
5428
5594
|
layers,
|
|
5429
5595
|
roadmap,
|
|
5430
5596
|
mappedModules,
|
|
@@ -5558,6 +5724,15 @@ function renderExplanationHuman(exp) {
|
|
|
5558
5724
|
for (const m of exp.missingKnowledge) lines.push(`- ${m}`);
|
|
5559
5725
|
lines.push("");
|
|
5560
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
|
+
}
|
|
5561
5736
|
const assessment = assessPhase(exp);
|
|
5562
5737
|
lines.push("## Phase");
|
|
5563
5738
|
lines.push(`- Phase: ${assessment.phase}`);
|
|
@@ -5952,10 +6127,12 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
|
|
|
5952
6127
|
deliveryMix,
|
|
5953
6128
|
mappedModules,
|
|
5954
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.
|
|
5955
6132
|
handoff: {
|
|
5956
|
-
recommendedAgents: recommendedAgentsForState(state),
|
|
5957
|
-
nextSteps: nextStepsForState2(state),
|
|
5958
|
-
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,
|
|
5959
6136
|
operatingRules: OPERATING_RULES
|
|
5960
6137
|
}
|
|
5961
6138
|
};
|
|
@@ -5965,9 +6142,6 @@ function serializeContextPackJson(pack) {
|
|
|
5965
6142
|
}
|
|
5966
6143
|
|
|
5967
6144
|
// src/templates/context-pack-template.ts
|
|
5968
|
-
function stateLabel2(state) {
|
|
5969
|
-
return state === "pre-ai" ? "pre-AI" : state;
|
|
5970
|
-
}
|
|
5971
6145
|
function renderContextPack(pack) {
|
|
5972
6146
|
const { project, scan: scan2, knowledge, missing, handoff } = pack;
|
|
5973
6147
|
const parts = [];
|
|
@@ -6108,11 +6282,11 @@ function renderContextPack(pack) {
|
|
|
6108
6282
|
parts.push("_None \u2014 all expected context is present._\n");
|
|
6109
6283
|
}
|
|
6110
6284
|
parts.push("## Recommended Agent Handoff\n");
|
|
6111
|
-
parts.push(`Recommended
|
|
6285
|
+
parts.push(`Recommended next for the **${pack.phase.phase}** phase:
|
|
6112
6286
|
`);
|
|
6113
6287
|
parts.push(handoff.recommendedAgents.map((a, i) => `${i + 1}. ${a}`).join("\n") + "\n");
|
|
6114
6288
|
if (handoff.nextSteps.length > 0) {
|
|
6115
|
-
parts.push("Next
|
|
6289
|
+
parts.push("Next step:\n");
|
|
6116
6290
|
parts.push(handoff.nextSteps.map((s) => `- ${s}`).join("\n") + "\n");
|
|
6117
6291
|
}
|
|
6118
6292
|
parts.push("## Instructions for the LLM\n");
|
|
@@ -6211,7 +6385,7 @@ function buildUnderstandPlan(dir, config) {
|
|
|
6211
6385
|
}
|
|
6212
6386
|
|
|
6213
6387
|
// src/templates/understand-template.ts
|
|
6214
|
-
function
|
|
6388
|
+
function stateLabel2(state) {
|
|
6215
6389
|
return state === "pre-ai" ? "pre-AI" : state;
|
|
6216
6390
|
}
|
|
6217
6391
|
function agentName(file) {
|
|
@@ -6234,7 +6408,7 @@ function renderUnderstand(plan) {
|
|
|
6234
6408
|
].join("\n") + "\n"
|
|
6235
6409
|
);
|
|
6236
6410
|
parts.push("## Recommended Agent Flow\n");
|
|
6237
|
-
parts.push(`Recommended order for a ${
|
|
6411
|
+
parts.push(`Recommended order for a ${stateLabel2(project.state)} project:
|
|
6238
6412
|
`);
|
|
6239
6413
|
parts.push(
|
|
6240
6414
|
steps.map((s, i) => {
|
|
@@ -6986,6 +7160,64 @@ function runAdd(moduleName, opts = {}, dir = cwd()) {
|
|
|
6986
7160
|
// src/core/ownership-suggest.ts
|
|
6987
7161
|
import matter5 from "gray-matter";
|
|
6988
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
|
+
}
|
|
6989
7221
|
function toStringArray3(value) {
|
|
6990
7222
|
return Array.isArray(value) ? value.map((v) => String(v)).filter(Boolean) : [];
|
|
6991
7223
|
}
|
|
@@ -7131,7 +7363,19 @@ async function chooseGlobs(suggested) {
|
|
|
7131
7363
|
placeholder: "src/payments/**",
|
|
7132
7364
|
validate: (v) => v.trim().length === 0 ? "A glob is required." : void 0
|
|
7133
7365
|
});
|
|
7134
|
-
|
|
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);
|
|
7135
7379
|
addMore = await confirm2({ message: "Add another glob?", initialValue: false });
|
|
7136
7380
|
}
|
|
7137
7381
|
return [...new Set(chosen)];
|