ucode-agent 1.63.0 → 1.65.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.
@@ -0,0 +1,83 @@
1
+ /**
2
+ * lessons.js — the mistakes ucode keeps catching, remembered across sessions.
3
+ *
4
+ * Every fix round is ucode finding something wrong with what the model built.
5
+ * The same few things come up build after build: a script that does not parse,
6
+ * a button wired to nothing, a list that forgets itself on reload. Counting
7
+ * them costs nothing, and the ones that keep happening go into the system
8
+ * prompt as a short warning — said before the build, where it is free, instead
9
+ * of after it, where it costs a fix round.
10
+ *
11
+ * The lessons themselves are fixed sentences, not something the model writes:
12
+ * a note in the prompt of every future build should never be a model's guess.
13
+ *
14
+ * Kept in ~/.ucode/lessons.json. Failing to read or write it never matters.
15
+ */
16
+
17
+ import { promises as fs } from 'node:fs';
18
+ import os from 'node:os';
19
+ import path from 'node:path';
20
+
21
+ export const LESSONS_FILE = path.join(os.homedir(), '.ucode', 'lessons.json');
22
+
23
+ const KINDS = [
24
+ { id: 'js-parse', test: /does not parse|SyntaxError|Unexpected token/i,
25
+ lesson: 'Scripts have failed to parse. Before finishing, check every script you wrote for unclosed brackets, braces and template strings.' },
26
+ { id: 'css-var', test: /custom propert(?:y is|ies are) used and never defined/i,
27
+ lesson: 'Stylesheets have used custom properties that were never defined. Define every token in :root before using it.' },
28
+ { id: 'dead-button', test: /NOTHING HAPPENS/,
29
+ lesson: 'Buttons have done nothing when clicked. Wire every control to a handler, attached after its element exists.' },
30
+ { id: 'add-broken', test: /ADDING DOES NOT WORK/,
31
+ lesson: 'Adding an item has failed. The form handler must prevent the default, read the inputs, update the list, render and save.' },
32
+ { id: 'no-persist', test: /works until you refresh/i,
33
+ lesson: 'Data has been lost on reload. Save to localStorage on every change and load it when the page starts.' },
34
+ { id: 'console', test: /Console errors/i,
35
+ lesson: 'Pages have thrown console errors. Check that every element you query exists, and wrap JSON.parse of saved data in try/catch.' },
36
+ { id: 'type-errors', test: /error TS\d+/,
37
+ lesson: 'TypeScript errors have come back. Match prop and function types exactly, and check an import exists before using it.' },
38
+ { id: 'tests', test: /tests covering your change fail/i,
39
+ lesson: 'Changes have broken existing tests. Read a function\'s tests before changing what it returns.' },
40
+ { id: 'generic', test: /generated look/i,
41
+ lesson: 'Designs have come out generic: the starter\'s colours, Inter, purple gradients. Choose the accent and typeface first, and use them.' },
42
+ ];
43
+
44
+ /** Which kinds of mistake a block of problems text contains. */
45
+ export function kindsIn(problems) {
46
+ const text = String(problems ?? '');
47
+ return KINDS.filter((k) => k.test.test(text)).map((k) => k.id);
48
+ }
49
+
50
+ async function readCounts(file) {
51
+ try {
52
+ const data = JSON.parse(await fs.readFile(file, 'utf8'));
53
+ return data && typeof data === 'object' && !Array.isArray(data) ? data : {};
54
+ } catch {
55
+ return {};
56
+ }
57
+ }
58
+
59
+ /** Count the mistakes in this fix round. Never throws. */
60
+ export async function noteMistakes(problems, file = LESSONS_FILE) {
61
+ const found = kindsIn(problems);
62
+ if (!found.length || process.env.UCODE_LESSONS === '0') return;
63
+ try {
64
+ const counts = await readCounts(file);
65
+ const next = { ...counts };
66
+ for (const id of found) next[id] = (Number(next[id]) || 0) + 1;
67
+ await fs.mkdir(path.dirname(file), { recursive: true });
68
+ const temp = `${file}.${process.pid}.tmp`;
69
+ await fs.writeFile(temp, JSON.stringify(next, null, 2));
70
+ await fs.rename(temp, file);
71
+ } catch { /* a lesson not saved is a lesson not learned, never a broken turn */ }
72
+ }
73
+
74
+ /** The lessons worth repeating: seen at least `min` times, the commonest first. */
75
+ export async function lessonsText(file = LESSONS_FILE, { min = 2, max = 3 } = {}) {
76
+ if (process.env.UCODE_LESSONS === '0') return '';
77
+ const counts = await readCounts(file);
78
+ const top = KINDS
79
+ .filter((k) => (Number(counts[k.id]) || 0) >= min)
80
+ .sort((a, b) => counts[b.id] - counts[a.id])
81
+ .slice(0, max);
82
+ return top.map((k) => `- ${k.lesson}`).join('\n');
83
+ }