(
messages: Message[],
sessionId: string,
options: AgentOptions,
)
| 901 | const { result } = await evalSkillMd(this.cwd, candidate.skillMd, { noCache: true }); |
| 902 | if (result) { |
| 903 | const fsmod = await import('fs'); |
| 904 | const pathmod = await import('path'); |
| 905 | const file = pathmod.join(candidatesDir(), candidate.name, 'SKILL.md'); |
| 906 | const updated = upsertEvalSection(candidate.skillMd, formatEvalSection(result, skillContentHash(candidate.skillMd))); |
| 907 | await fsmod.promises.writeFile(file, updated, 'utf-8'); |
| 908 | await recordLearningEvent({ event: 'eval', name: candidate.name, evalStatus: result.status }); |
| 909 | yield { type: 'notice', data: { message: `🧪 Auto-eval of "${candidate.name}": ${result.status}.` } }; |
| 910 | } |
| 911 | } catch (e: any) { |
| 912 | logger.debug('Auto-eval after capture skipped', { err: e?.message }); |
| 913 | } |
| 914 | } |
| 915 | } else { |
| 916 | logger.debug('Skill capture skipped', { reason: elig.reason }); |
| 917 | } |
| 918 | } catch (e: any) { |
| 919 | logger.debug('Skill capture skipped (error)', { err: e?.message }); |
| 920 | } |
| 921 | } |
| 922 | // ── Code-graph skill SUGGESTION ── Whenever nothing was captured (learning off, OR on but |
| 923 | // the task wasn't capture-eligible), still nudge the user when the work looks like a REUSABLE |
| 924 | // pattern, judged from the SHAPE of the change via the code graph (focused + cohesive + |
| 925 | // multi-file). The judgment a code-graph-less agent can't make. A gentle one-liner; off via |
| 926 | // learning.suggestSkills:false; conservative (only fires for real patterns), never duplicative. |
| 927 | if (!capturedThisRun && learningCfg?.suggestSkills !== false && changedFiles.length >= 2) { |
| 928 | try { |
| 929 | const { suggestSkillFromSession, commonArea } = await import('../skills/learning/skill-suggest.js'); |
| 930 | const area = commonArea(changedFiles); |
| 931 | const inArea = changedFiles.filter(f => f.split('/').slice(0, 2).join('/') === area).length; |
| 932 | const cohesion = changedFiles.length ? inArea / changedFiles.length : 0; |
| 933 | const s = suggestSkillFromSession({ prompt: String(firstUserMsg), changedFiles, cohesion }); |
| 934 | if (s.worth) { |
| 935 | const how = learningCfg?.enabled |
| 936 | ? `run \`qodex skill candidates\` to capture it` |
| 937 | : `enable \`learning.enabled\` to auto-capture skills like this`; |
| 938 | yield { type: 'notice', data: { message: `💡 This looks reusable ("${s.proposedName}") — ${s.reason} ${how}.` } }; |
| 939 | } |
| 940 | } catch { /* best-effort */ } |
| 941 | } |
| 942 | } else if (reachedFinal && !merged) { |
| 943 | if (!finishResult.merged && finishResult.reason === 'empty') { |
| 944 | yield { type: 'notice', data: { message: '🔒 Sandbox: nothing to merge (no committed changes)' } }; |
| 945 | } else { |
| 946 | // Real merge failure (conflict/error): the work is NOT lost — it's still |
| 947 | // on the sandbox branch. Tell the user distinctly so they don't think |
| 948 | // their changes simply vanished. |
| 949 | const reason = !finishResult.merged ? finishResult.reason : 'error'; |
| 950 | const errSuffix = (!finishResult.merged && finishResult.error) ? ` (${finishResult.error})` : ''; |
| 951 | const branchSuffix = sandboxBranch ? ` Your work is preserved on branch '${sandboxBranch}'.` : ' Your work is preserved on the sandbox branch.'; |
| 952 | yield { type: 'notice', data: { message: `🔒 Sandbox: FAILED to merge your changes onto your branch (${reason})${errSuffix}.${branchSuffix}` } }; |
| 953 | } |
| 954 | } else { |
| 955 | yield { type: 'notice', data: { message: '🔒 Sandbox: task did not complete — your branch was left untouched' } }; |
| 956 | } |
| 957 | |
| 958 | // Ground-truth trust receipt for unattended/scheduled runs. QodeX writes it from ITS OWN |
| 959 | // signals — the git diff + the checkers it actually ran — so the model can't fabricate it. |
| 960 | await this.writeRunReceipt({ reachedFinal, merged, finalContent, changedFiles, signal: options.signal }); |
no test coverage detected