MCPcopy Create free account
hub / github.com/QodeXcli/QodeX / execute

Method execute

src/tools/builtin/suggest-skill.ts:21–77  ·  view source on GitHub ↗
(args: z.infer<typeof Args>, ctx: ToolContext)

Source from the content-addressed store, hash-verified

19 argsSchema = Args;
20
21 async execute(args: z.infer<typeof Args>, ctx: ToolContext): Promise<ToolResult> {
22 const cwd = ctx.cwd ?? process.cwd();
23
24 // Files changed this session (working tree vs HEAD).
25 const changedFiles = await (async () => {
26 try {
27 const { git } = await import('../git/git-runner.js');
28 const d = await git(['diff', 'HEAD', '--name-only'], { cwd });
29 return d.exitCode === 0 ? d.stdout.split('\n').map(s => s.trim()).filter(Boolean) : [];
30 } catch { return []; }
31 })();
32
33 const { suggestSkillFromSession, draftCandidateSkill, commonArea } = await import('../../skills/learning/skill-suggest.js');
34 const area = commonArea(changedFiles);
35
36 // Cohesion = how concentrated the change is in one module (a code-organization signal), with a
37 // best-effort boost when the code graph shows the touched files are import-connected.
38 const inArea = changedFiles.filter(f => f.split('/').slice(0, 2).join('/') === area).length;
39 let cohesion = changedFiles.length ? inArea / changedFiles.length : 0;
40 let touchedSymbols: string[] | undefined;
41 try {
42 const { getSymbolGraph, dependencyContextFor } = await import('../../context/symbol-graph.js');
43 const graph = await getSymbolGraph(cwd, { signal: AbortSignal.timeout(3000) });
44 if (graph && changedFiles.length) {
45 const deps = dependencyContextFor(graph, changedFiles);
46 touchedSymbols = [...new Set(deps.map((d: any) => d.symbol ?? d.name).filter(Boolean))].slice(0, 6);
47 // connected cluster (the change's files reference each other) → real reusable pattern
48 if (deps.some((d: any) => (d.neighbors ?? d.deps ?? []).length > 0)) cohesion = Math.min(1, cohesion + 0.15);
49 }
50 } catch { /* graph optional */ }
51
52 const input = { prompt: args.task, changedFiles, cohesion, touchedSymbols };
53 const s = suggestSkillFromSession(input);
54
55 // Proactive: when the shape clearly says "reusable pattern", draft the candidate now (quarantined)
56 // so the user only reviews + promotes. Skipped if draft:false, or if an identically-named candidate
57 // already exists (don't spam duplicates).
58 let drafted: string | null = null;
59 if (s.worth && args.draft !== false) {
60 try {
61 const { listCandidates, writeCandidate } = await import('../../skills/learning/candidate-store.js');
62 const existing = await listCandidates().catch(() => []);
63 if (!existing.some(c => c.name === s.proposedName)) {
64 const c = draftCandidateSkill(input, s, new Date().toISOString());
65 await writeCandidate(c);
66 drafted = c.name;
67 }
68 } catch { /* drafting is best-effort — the recommendation still stands */ }
69 }
70
71 const head = s.worth
72 ? (drafted
73 ? `💡 Drafted a candidate skill: "${drafted}" (quarantined)\n ${s.reason}\n → review with \`qodex skill candidates\`, promote with \`qodex skill curate\`.`
74 : `💡 Worth saving as a skill: "${s.proposedName}"\n ${s.reason}\n → review with \`qodex skill candidates\`, then \`qodex skill curate\`.`)
75 : `No skill suggested. ${s.reason}`;
76 return { content: head, metadata: { worth: s.worth, score: s.score, proposedName: s.proposedName, area: s.area, changedFiles: changedFiles.length, drafted: !!drafted } };
77 }
78}

Callers

nothing calls this directly

Calls 8

commonAreaFunction · 0.85
getSymbolGraphFunction · 0.85
dependencyContextForFunction · 0.85
suggestSkillFromSessionFunction · 0.85
listCandidatesFunction · 0.85
draftCandidateSkillFunction · 0.85
writeCandidateFunction · 0.85
gitFunction · 0.50

Tested by

no test coverage detected