@nxuss/lemma 1.27.1 → 1.29.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/README.md +61 -15
- package/bin/brain-ingest.js +64 -2
- package/dashboard/dist/assets/index-Dh-whqJ7.js +481 -0
- package/dashboard/dist/assets/index-Dh-whqJ7.js.map +1 -0
- package/dashboard/dist/assets/{index-DtEKr0hI.css → index-Yt1H2B7F.css} +1 -1
- package/dashboard/dist/index.html +2 -2
- package/dist/cjs/cli/gitHooks.d.ts +28 -0
- package/dist/cjs/cli/gitHooks.d.ts.map +1 -0
- package/dist/cjs/cli/gitHooks.js +112 -0
- package/dist/cjs/cli/gitHooks.js.map +1 -0
- package/dist/cjs/cli/lemma-proxy.d.ts.map +1 -1
- package/dist/cjs/cli/lemma-proxy.js +26 -0
- package/dist/cjs/cli/lemma-proxy.js.map +1 -1
- package/dist/cjs/mcp/tool-profiles.d.ts.map +1 -1
- package/dist/cjs/mcp/tool-profiles.js +8 -0
- package/dist/cjs/mcp/tool-profiles.js.map +1 -1
- package/dist/cjs/mcp/tools/memory.d.ts.map +1 -1
- package/dist/cjs/mcp/tools/memory.js +57 -0
- package/dist/cjs/mcp/tools/memory.js.map +1 -1
- package/dist/cjs/mcp/tools/misc.d.ts.map +1 -1
- package/dist/cjs/mcp/tools/misc.js +16 -1
- package/dist/cjs/mcp/tools/misc.js.map +1 -1
- package/dist/cjs/mcp/tools/workspace.d.ts.map +1 -1
- package/dist/cjs/mcp/tools/workspace.js +17 -3
- package/dist/cjs/mcp/tools/workspace.js.map +1 -1
- package/dist/cjs/mcp/tools.d.ts.map +1 -1
- package/dist/cjs/mcp/tools.js +2 -0
- package/dist/cjs/mcp/tools.js.map +1 -1
- package/dist/cjs/subconscious/GitIngest.d.ts.map +1 -1
- package/dist/cjs/subconscious/GitIngest.js +49 -1
- package/dist/cjs/subconscious/GitIngest.js.map +1 -1
- package/dist/cjs/subconscious/TeamBrain.d.ts +82 -0
- package/dist/cjs/subconscious/TeamBrain.d.ts.map +1 -0
- package/dist/cjs/subconscious/TeamBrain.js +543 -0
- package/dist/cjs/subconscious/TeamBrain.js.map +1 -0
- package/dist/cjs/subconscious/TheBrainV2.d.ts +19 -2
- package/dist/cjs/subconscious/TheBrainV2.d.ts.map +1 -1
- package/dist/cjs/subconscious/TheBrainV2.js +23 -0
- package/dist/cjs/subconscious/TheBrainV2.js.map +1 -1
- package/dist/cjs/utils/FixCapture.d.ts +66 -0
- package/dist/cjs/utils/FixCapture.d.ts.map +1 -0
- package/dist/cjs/utils/FixCapture.js +338 -0
- package/dist/cjs/utils/FixCapture.js.map +1 -0
- package/dist/esm/cli/gitHooks.d.ts +28 -0
- package/dist/esm/cli/gitHooks.d.ts.map +1 -0
- package/dist/esm/cli/gitHooks.js +106 -0
- package/dist/esm/cli/gitHooks.js.map +1 -0
- package/dist/esm/cli/lemma-proxy.d.ts.map +1 -1
- package/dist/esm/cli/lemma-proxy.js +26 -0
- package/dist/esm/cli/lemma-proxy.js.map +1 -1
- package/dist/esm/mcp/tool-profiles.d.ts.map +1 -1
- package/dist/esm/mcp/tool-profiles.js +8 -0
- package/dist/esm/mcp/tool-profiles.js.map +1 -1
- package/dist/esm/mcp/tools/memory.d.ts.map +1 -1
- package/dist/esm/mcp/tools/memory.js +57 -0
- package/dist/esm/mcp/tools/memory.js.map +1 -1
- package/dist/esm/mcp/tools/misc.d.ts.map +1 -1
- package/dist/esm/mcp/tools/misc.js +16 -1
- package/dist/esm/mcp/tools/misc.js.map +1 -1
- package/dist/esm/mcp/tools/workspace.d.ts.map +1 -1
- package/dist/esm/mcp/tools/workspace.js +17 -3
- package/dist/esm/mcp/tools/workspace.js.map +1 -1
- package/dist/esm/mcp/tools.d.ts.map +1 -1
- package/dist/esm/mcp/tools.js +2 -0
- package/dist/esm/mcp/tools.js.map +1 -1
- package/dist/esm/subconscious/GitIngest.d.ts.map +1 -1
- package/dist/esm/subconscious/GitIngest.js +49 -1
- package/dist/esm/subconscious/GitIngest.js.map +1 -1
- package/dist/esm/subconscious/TeamBrain.d.ts +82 -0
- package/dist/esm/subconscious/TeamBrain.d.ts.map +1 -0
- package/dist/esm/subconscious/TeamBrain.js +533 -0
- package/dist/esm/subconscious/TeamBrain.js.map +1 -0
- package/dist/esm/subconscious/TheBrainV2.d.ts +19 -2
- package/dist/esm/subconscious/TheBrainV2.d.ts.map +1 -1
- package/dist/esm/subconscious/TheBrainV2.js +23 -0
- package/dist/esm/subconscious/TheBrainV2.js.map +1 -1
- package/dist/esm/utils/FixCapture.d.ts +66 -0
- package/dist/esm/utils/FixCapture.d.ts.map +1 -0
- package/dist/esm/utils/FixCapture.js +328 -0
- package/dist/esm/utils/FixCapture.js.map +1 -0
- package/package.json +1 -1
- package/dashboard/dist/assets/index-DiidVeI5.js +0 -481
- package/dashboard/dist/assets/index-DiidVeI5.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FixCapture.d.ts","sourceRoot":"","sources":["../../../src/utils/FixCapture.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAOH,OAAO,EAA6B,UAAU,EAAE,MAAM,4BAA4B,CAAC;AA8BnF;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAuBzD;AAaD,MAAM,WAAW,cAAc;IAC7B,iFAAiF;IACjF,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IAC1D,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AA2DD,iGAAiG;AACjG,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAyBtF;AAwBD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,iGAAiG;IACjG,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,GAAG,cAAc,CA6EjB;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,cAAc,CAchF;AAED,oFAAoF;AACpF,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAU3D"}
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* FixCapture — turns a red → green transition into a memory, with nobody asking.
|
|
4
|
+
*
|
|
5
|
+
* The auto-capture that existed before this (test_oracle's "tests passing after changes to
|
|
6
|
+
* a.ts") stored the one thing nobody searches for: that a suite passed. What a later session
|
|
7
|
+
* actually has in hand is the *error* — CLAUDE.md tells it to search_memory with the error
|
|
8
|
+
* message — and the answer it needs is "this exact failure was fixed by changing these files".
|
|
9
|
+
* That pairing only exists across two runs of the same command, so this module keeps the
|
|
10
|
+
* failing run's signature and the hash of every dirty file at that moment, and when the same
|
|
11
|
+
* command next passes, the files whose content moved in between are the fix.
|
|
12
|
+
*
|
|
13
|
+
* State lives on disk (`.lemma/session/fix-capture.json`), not in the process: the failing run
|
|
14
|
+
* and the passing one may arrive through different processes — the MCP server's
|
|
15
|
+
* run_workspace_command, or the host's own Bash tool relayed by a PostToolUse hook
|
|
16
|
+
* (`lemma-brain-ingest --hook-result`). Agents mostly run tests through their native shell, so
|
|
17
|
+
* a capture that only saw Lemma's tools would see a fraction of the fixes.
|
|
18
|
+
*
|
|
19
|
+
* It never guesses: a pass with no file changed since the failure was flaky, and is recorded as
|
|
20
|
+
* nothing at all.
|
|
21
|
+
*/
|
|
22
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
23
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.isVerdictCommand = isVerdictCommand;
|
|
27
|
+
exports.extractFailure = extractFailure;
|
|
28
|
+
exports.recordCommandOutcome = recordCommandOutcome;
|
|
29
|
+
exports.recordHookPayload = recordHookPayload;
|
|
30
|
+
exports.inferVerdict = inferVerdict;
|
|
31
|
+
const fs_1 = __importDefault(require("fs"));
|
|
32
|
+
const path_1 = __importDefault(require("path"));
|
|
33
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
34
|
+
const child_process_1 = require("child_process");
|
|
35
|
+
const AffectedTests_1 = require("./AffectedTests");
|
|
36
|
+
const TheBrainV2_1 = require("../subconscious/TheBrainV2");
|
|
37
|
+
const TeamBrain_1 = require("../subconscious/TeamBrain");
|
|
38
|
+
const STATE_FILE = path_1.default.join('.lemma', 'session', 'fix-capture.json');
|
|
39
|
+
/** A failure older than this is no longer the thing the next pass fixed. */
|
|
40
|
+
const PENDING_TTL_MS = 24 * 60 * 60 * 1000;
|
|
41
|
+
const MAX_SIGNATURE_LINES = 20;
|
|
42
|
+
const MAX_SIGNATURE_CHARS = 1500;
|
|
43
|
+
const MAX_DIFF_CHARS = 2500;
|
|
44
|
+
/** A "fix" touching more files than this is a refactor or a branch switch, not a fix worth recalling. */
|
|
45
|
+
const MAX_FIX_FILES = 15;
|
|
46
|
+
/** Runners whose exit code is a verdict on the code by themselves. */
|
|
47
|
+
const VERDICT_TOOLS = new Set([
|
|
48
|
+
'jest', 'vitest', 'mocha', 'ava', 'tap', 'pytest', 'tox', 'tsc', 'vue-tsc', 'eslint', 'biome',
|
|
49
|
+
'ruff', 'mypy', 'pyright', 'rspec', 'phpunit', 'mvn', 'gradle', 'gradlew', 'playwright', 'cypress',
|
|
50
|
+
]);
|
|
51
|
+
/** Tools that are a verdict only with the right subcommand (`go test`, not `go run`). */
|
|
52
|
+
const VERDICT_SUBCOMMANDS = {
|
|
53
|
+
go: /^(test|vet|build)$/,
|
|
54
|
+
cargo: /^(test|build|check|clippy|nextest)$/,
|
|
55
|
+
dotnet: /^(test|build)$/,
|
|
56
|
+
swift: /^(test|build)$/,
|
|
57
|
+
make: /^(test|check|lint)$/,
|
|
58
|
+
};
|
|
59
|
+
/** Package-manager scripts that conventionally run a verdict. */
|
|
60
|
+
const VERDICT_SCRIPT_RE = /^(test|build|lint|typecheck|type-check|check|tsc|ci)(:.*)?$/;
|
|
61
|
+
/** Launchers that run the next token as the real command. */
|
|
62
|
+
const LAUNCHERS = new Set(['npx', 'bunx', 'pnpx', 'env', 'time', 'uv', 'poetry']);
|
|
63
|
+
/**
|
|
64
|
+
* Whether a command's pass/fail is a verdict on the code. Judged per shell segment on the
|
|
65
|
+
* command's head, not anywhere in the text: `cat jest.config.js` mentions jest and is no
|
|
66
|
+
* verdict, `CI=1 npx jest` and `cd app && npm test` are.
|
|
67
|
+
*/
|
|
68
|
+
function isVerdictCommand(command) {
|
|
69
|
+
for (const segment of (command || '').split(/&&|\|\||;|\|/)) {
|
|
70
|
+
let tokens = segment.trim().split(/\s+/).filter(Boolean);
|
|
71
|
+
while (tokens.length && /^[A-Za-z_][A-Za-z0-9_]*=/.test(tokens[0]))
|
|
72
|
+
tokens = tokens.slice(1); // FOO=bar
|
|
73
|
+
// Launchers and their run/exec/dlx/-m forms: `npx jest`, `pnpm exec vitest`, `python -m pytest`, `uv run pytest`.
|
|
74
|
+
for (let guard = 0; guard < 4 && tokens.length > 1; guard++) {
|
|
75
|
+
const head = path_1.default.basename(tokens[0]);
|
|
76
|
+
if (LAUNCHERS.has(head)) {
|
|
77
|
+
tokens = tokens.slice(1);
|
|
78
|
+
if (/^(run|x)$/.test(tokens[0] || ''))
|
|
79
|
+
tokens = tokens.slice(1);
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (/^(pnpm|yarn|bun)$/.test(head) && /^(exec|dlx|x)$/.test(tokens[1])) {
|
|
83
|
+
tokens = tokens.slice(2);
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (/^python\d?(\.\d+)?$/.test(head) && tokens[1] === '-m') {
|
|
87
|
+
tokens = tokens.slice(2);
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
if (tokens.length === 0)
|
|
93
|
+
continue;
|
|
94
|
+
const head = path_1.default.basename(tokens[0]).replace(/\.(cmd|exe|sh)$/, '');
|
|
95
|
+
if (VERDICT_TOOLS.has(head))
|
|
96
|
+
return true;
|
|
97
|
+
const sub = VERDICT_SUBCOMMANDS[head];
|
|
98
|
+
if (sub && tokens[1] && sub.test(tokens[1]))
|
|
99
|
+
return true;
|
|
100
|
+
if (/^(npm|pnpm|yarn|bun)$/.test(head)) {
|
|
101
|
+
const script = tokens[1] === 'run' || tokens[1] === 'run-script' ? tokens[2] : tokens[1];
|
|
102
|
+
if (script && (VERDICT_SCRIPT_RE.test(script) || script === 't'))
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
function stateKey(command) {
|
|
109
|
+
return command.trim().replace(/\s+/g, ' ');
|
|
110
|
+
}
|
|
111
|
+
function readState(root) {
|
|
112
|
+
try {
|
|
113
|
+
const parsed = JSON.parse(fs_1.default.readFileSync(path_1.default.join(root, STATE_FILE), 'utf8'));
|
|
114
|
+
return parsed && typeof parsed === 'object' ? parsed : {};
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return {};
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function writeState(root, state) {
|
|
121
|
+
const p = path_1.default.join(root, STATE_FILE);
|
|
122
|
+
fs_1.default.mkdirSync(path_1.default.dirname(p), { recursive: true });
|
|
123
|
+
fs_1.default.writeFileSync(p, JSON.stringify(state), 'utf8');
|
|
124
|
+
}
|
|
125
|
+
function hashWorkingFile(root, rel) {
|
|
126
|
+
try {
|
|
127
|
+
return crypto_1.default.createHash('sha256').update(fs_1.default.readFileSync(path_1.default.join(root, rel))).digest('hex');
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
return 'DELETED';
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/** Lemma's own bookkeeping is never "the fix". */
|
|
134
|
+
function isOwnFile(rel) {
|
|
135
|
+
return rel.startsWith('.lemma/') || rel.startsWith('.lemma-brain/');
|
|
136
|
+
}
|
|
137
|
+
function dirtySnapshot(root) {
|
|
138
|
+
const out = {};
|
|
139
|
+
for (const rel of (0, AffectedTests_1.changedFilesFromGit)(root)) {
|
|
140
|
+
if (isOwnFile(rel))
|
|
141
|
+
continue;
|
|
142
|
+
out[rel] = hashWorkingFile(root, rel);
|
|
143
|
+
}
|
|
144
|
+
return out;
|
|
145
|
+
}
|
|
146
|
+
// Order matters: the first pattern that matches anywhere wins the headline, so the most
|
|
147
|
+
// specific, most searchable forms come first (a named JS/Python error, a tsc code, a failing
|
|
148
|
+
// test title) and the generic "error" line last.
|
|
149
|
+
const HEADLINE_PATTERNS = [
|
|
150
|
+
/\b[A-Z]\w*(Error|Exception)\b:?\s+\S.*/,
|
|
151
|
+
/error\s+TS\d+:.*/,
|
|
152
|
+
/^\s*●\s+(?!Console\b).+/,
|
|
153
|
+
/^\s*(✕|×|✗)\s+.+/,
|
|
154
|
+
/^FAILED\s+.+/,
|
|
155
|
+
/^\s*E\s{2,}.+/,
|
|
156
|
+
/\berror(\[E\d+\])?:\s+.+/i,
|
|
157
|
+
];
|
|
158
|
+
const SIGNATURE_LINE_RE = /(●\s|✕|×|✗|^FAIL\b|^FAILED\b|error\s+TS\d+|\b\w*(Error|Exception)\b|^\s*E\s{2,}|expected|received|assert|panicked|\berror(\[E\d+\])?:)/i;
|
|
159
|
+
/** The failure, compressed to what identifies it — test titles, error lines, their locations. */
|
|
160
|
+
function extractFailure(output) {
|
|
161
|
+
const lines = (output || '').split('\n').map((l) => l.replace(/\s+$/, ''));
|
|
162
|
+
let headline = '';
|
|
163
|
+
for (const re of HEADLINE_PATTERNS) {
|
|
164
|
+
for (const line of lines) {
|
|
165
|
+
const m = line.match(re);
|
|
166
|
+
if (m) {
|
|
167
|
+
headline = m[0].trim();
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (headline)
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
const picked = [];
|
|
175
|
+
const seen = new Set();
|
|
176
|
+
for (const line of lines) {
|
|
177
|
+
const t = line.trim();
|
|
178
|
+
if (!t || seen.has(t) || /node_modules/.test(t))
|
|
179
|
+
continue;
|
|
180
|
+
if (!SIGNATURE_LINE_RE.test(t))
|
|
181
|
+
continue;
|
|
182
|
+
seen.add(t);
|
|
183
|
+
picked.push(t);
|
|
184
|
+
if (picked.length >= MAX_SIGNATURE_LINES)
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
let signature = picked.join('\n');
|
|
188
|
+
if (!signature)
|
|
189
|
+
signature = lines.filter(Boolean).slice(-MAX_SIGNATURE_LINES).join('\n');
|
|
190
|
+
if (signature.length > MAX_SIGNATURE_CHARS)
|
|
191
|
+
signature = signature.slice(0, MAX_SIGNATURE_CHARS) + '\n…';
|
|
192
|
+
if (!headline)
|
|
193
|
+
headline = (picked[0] || lines.find((l) => l.trim()) || 'command failed').trim();
|
|
194
|
+
return { headline: headline.slice(0, 200), signature };
|
|
195
|
+
}
|
|
196
|
+
function diffFor(root, files) {
|
|
197
|
+
let diff = '';
|
|
198
|
+
try {
|
|
199
|
+
diff = (0, child_process_1.execFileSync)('git', ['-C', root, 'diff', '--no-color', 'HEAD', '--', ...files], {
|
|
200
|
+
encoding: 'utf8',
|
|
201
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
202
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
catch { /* no HEAD yet, or not a repo — the file list still stands */ }
|
|
206
|
+
if (diff.length > MAX_DIFF_CHARS)
|
|
207
|
+
diff = diff.slice(0, MAX_DIFF_CHARS) + '\n… (diff truncated)';
|
|
208
|
+
return diff.trim();
|
|
209
|
+
}
|
|
210
|
+
function isGitRepo(root) {
|
|
211
|
+
try {
|
|
212
|
+
(0, child_process_1.execFileSync)('git', ['-C', root, 'rev-parse', '--git-dir'], { stdio: 'ignore' });
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Feed one finished run of a command. Returns a note to append to the tool result (empty when
|
|
221
|
+
* the command isn't a verdict command or nothing happened worth mentioning).
|
|
222
|
+
*/
|
|
223
|
+
function recordCommandOutcome(input) {
|
|
224
|
+
const command = input.command || '';
|
|
225
|
+
if (!input.verdict && !isVerdictCommand(command))
|
|
226
|
+
return { note: '' };
|
|
227
|
+
const root = (0, TeamBrain_1.repoRoot)(input.cwd || process.cwd());
|
|
228
|
+
// Without git there's no way to tell which files the fix touched.
|
|
229
|
+
if (!isGitRepo(root))
|
|
230
|
+
return { note: '' };
|
|
231
|
+
const now = input.now ?? Date.now();
|
|
232
|
+
const key = stateKey(command);
|
|
233
|
+
const state = readState(root);
|
|
234
|
+
for (const [k, p] of Object.entries(state)) {
|
|
235
|
+
if (now - p.failedAt > PENDING_TTL_MS)
|
|
236
|
+
delete state[k];
|
|
237
|
+
}
|
|
238
|
+
if (!input.passed) {
|
|
239
|
+
const { headline, signature } = extractFailure(input.output);
|
|
240
|
+
// Keep the FIRST failure's dirty snapshot across repeated red runs: the fix is everything
|
|
241
|
+
// that changed since the suite first went red, not since the last of several attempts.
|
|
242
|
+
const prior = state[key];
|
|
243
|
+
state[key] = {
|
|
244
|
+
command: key,
|
|
245
|
+
failedAt: prior ? prior.failedAt : now,
|
|
246
|
+
headline,
|
|
247
|
+
signature,
|
|
248
|
+
dirty: prior ? prior.dirty : dirtySnapshot(root),
|
|
249
|
+
};
|
|
250
|
+
writeState(root, state);
|
|
251
|
+
return { note: '', pending: true };
|
|
252
|
+
}
|
|
253
|
+
const pending = state[key];
|
|
254
|
+
if (!pending) {
|
|
255
|
+
writeState(root, state);
|
|
256
|
+
return { note: '' };
|
|
257
|
+
}
|
|
258
|
+
delete state[key];
|
|
259
|
+
writeState(root, state);
|
|
260
|
+
// The fix = files whose content differs from what it was when the run went red: edited,
|
|
261
|
+
// newly created, deleted, or reverted back to HEAD.
|
|
262
|
+
const current = dirtySnapshot(root);
|
|
263
|
+
const candidates = new Set([...Object.keys(current), ...Object.keys(pending.dirty)]);
|
|
264
|
+
const fixFiles = [...candidates]
|
|
265
|
+
.filter((rel) => (current[rel] ?? hashWorkingFile(root, rel)) !== pending.dirty[rel])
|
|
266
|
+
.sort();
|
|
267
|
+
if (fixFiles.length === 0) {
|
|
268
|
+
return { note: `\n\n[Lemma] \`${key}\` passed with no file changed since it failed — looks flaky, so nothing was stored.` };
|
|
269
|
+
}
|
|
270
|
+
if (fixFiles.length > MAX_FIX_FILES) {
|
|
271
|
+
return { note: `\n\n[Lemma] \`${key}\` went green, but ${fixFiles.length} files changed since it failed — too broad to record as one fix, nothing stored.` };
|
|
272
|
+
}
|
|
273
|
+
const diff = diffFor(root, fixFiles);
|
|
274
|
+
const query = `fix: ${pending.headline}`;
|
|
275
|
+
const response = [
|
|
276
|
+
`\`${key}\` was failing with:`,
|
|
277
|
+
pending.signature,
|
|
278
|
+
'',
|
|
279
|
+
`It passed after changes to:`,
|
|
280
|
+
...fixFiles.map((f) => `- ${f}${hashWorkingFile(root, f) === 'DELETED' ? ' (deleted)' : ''}`),
|
|
281
|
+
...(diff ? ['', 'Diff vs HEAD at capture time (may include unrelated edits already in those files):', diff] : []),
|
|
282
|
+
].join('\n');
|
|
283
|
+
const brain = input.brain ?? (0, TheBrainV2_1.getBrain)();
|
|
284
|
+
const existing = fixFiles.filter((f) => fs_1.default.existsSync(path_1.default.join(root, f))).map((f) => path_1.default.join(root, f));
|
|
285
|
+
const res = brain.store(query, response, 'fix_capture_auto', 0.92, existing.length ? existing : undefined, (0, TheBrainV2_1.deriveProjectId)(root), 'confirmed', undefined, undefined, undefined, undefined, { source: 'auto-fix' });
|
|
286
|
+
if (!res.stored || !res.id) {
|
|
287
|
+
return { note: '' };
|
|
288
|
+
}
|
|
289
|
+
return {
|
|
290
|
+
captured: { id: res.id, query, files: fixFiles },
|
|
291
|
+
note: `\n\n[Lemma] Captured this fix in Brain (id ${res.id}): "${query.slice(0, 120)}" → ${fixFiles.length} file(s). ` +
|
|
292
|
+
`search_memory with that error will surface it; it goes stale as soon as those files change. ` +
|
|
293
|
+
`If this wasn't really the fix, forget_memory ${res.id}.`,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Entry point for a host PostToolUse hook (Claude Code shape: `tool_name`, `tool_input.command`,
|
|
298
|
+
* `tool_response`). Exit status isn't always in the payload, so when it's missing the verdict is
|
|
299
|
+
* read from the output's own summary lines — and when that's ambiguous, nothing is recorded
|
|
300
|
+
* rather than a guess.
|
|
301
|
+
*/
|
|
302
|
+
function recordHookPayload(payload, cwd) {
|
|
303
|
+
if (!payload || typeof payload !== 'object')
|
|
304
|
+
return { note: '' };
|
|
305
|
+
const p = payload;
|
|
306
|
+
if (p.tool_name && p.tool_name !== 'Bash')
|
|
307
|
+
return { note: '' };
|
|
308
|
+
const command = p.tool_input?.command;
|
|
309
|
+
if (typeof command !== 'string')
|
|
310
|
+
return { note: '' };
|
|
311
|
+
const resp = p.tool_response ?? {};
|
|
312
|
+
const output = typeof resp === 'string' ? resp : [resp.stdout, resp.stderr, resp.output].filter((s) => typeof s === 'string').join('\n');
|
|
313
|
+
if (resp.interrupted === true)
|
|
314
|
+
return { note: '' };
|
|
315
|
+
const code = [resp.exit_code, resp.exitCode, resp.code, p.exit_code].find((c) => typeof c === 'number');
|
|
316
|
+
const passed = typeof code === 'number' ? code === 0 : inferVerdict(output);
|
|
317
|
+
if (passed === null)
|
|
318
|
+
return { note: '' };
|
|
319
|
+
return recordCommandOutcome({ command, cwd: cwd || p.cwd, passed, output });
|
|
320
|
+
}
|
|
321
|
+
/** Pass/fail from a runner's own summary line; null when the output doesn't say. */
|
|
322
|
+
function inferVerdict(output) {
|
|
323
|
+
const o = output || '';
|
|
324
|
+
// Counts are [1-9]-anchored: "0 failed", "Found 0 errors" and "0 errors, 0 warnings" are
|
|
325
|
+
// what a clean run prints, and reading them as failures would invent a red run.
|
|
326
|
+
if (/Tests?:\s+.*\b[1-9]\d* failed\b/.test(o) || /\b[1-9]\d* failed\b.*\bpassed\b/.test(o) || /^FAIL\s/m.test(o))
|
|
327
|
+
return false;
|
|
328
|
+
if (/error\s+TS\d+:/.test(o) || /Found [1-9]\d* errors?\b/.test(o))
|
|
329
|
+
return false;
|
|
330
|
+
if (/✖ [1-9]\d* problems?/.test(o) || /\b[1-9]\d* errors?\b.*\bwarnings?\b/.test(o))
|
|
331
|
+
return false;
|
|
332
|
+
if (/^=+ .*\bfailed\b.* =+$/m.test(o))
|
|
333
|
+
return false;
|
|
334
|
+
if (/Tests?:\s+.*\b\d+ passed\b/.test(o) || /^=+ .*\bpassed\b.* =+$/m.test(o))
|
|
335
|
+
return true;
|
|
336
|
+
return null;
|
|
337
|
+
}
|
|
338
|
+
//# sourceMappingURL=FixCapture.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FixCapture.js","sourceRoot":"","sources":["../../../src/utils/FixCapture.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Installs the git hooks that feed the Brain without anyone remembering to: post-commit ingests
|
|
3
|
+
* the new commit, post-merge/post-checkout pull the team's shared memories (`.lemma-brain/`).
|
|
4
|
+
*
|
|
5
|
+
* `lemma-brain-ingest` shipped with a post-commit recipe in its header comment and nothing ever
|
|
6
|
+
* installed it — on the author's own machine the corpus held 0 commit memories. This is the
|
|
7
|
+
* missing install step, with the constraints a hook installer owes the repo it touches:
|
|
8
|
+
*
|
|
9
|
+
* - Append, never overwrite. An existing hook keeps running exactly as before; Lemma's part is
|
|
10
|
+
* one delimited block, and re-running init replaces that block in place (idempotent).
|
|
11
|
+
* - Never break a commit. Every line is guarded by `command -v`, backgrounded, and silenced.
|
|
12
|
+
* - Respect `core.hooksPath`. `git rev-parse --git-path hooks` resolves it; when it points inside
|
|
13
|
+
* the working tree (husky and friends — versioned hooks), the files are the team's, so they are
|
|
14
|
+
* left alone and the caller gets the lines to add instead.
|
|
15
|
+
*/
|
|
16
|
+
export interface HookInstallResult {
|
|
17
|
+
installed: string[];
|
|
18
|
+
updated: string[];
|
|
19
|
+
unchanged: string[];
|
|
20
|
+
/** Set when hooks live in a versioned directory Lemma won't write to; holds what to add by hand. */
|
|
21
|
+
manual?: {
|
|
22
|
+
hooksDir: string;
|
|
23
|
+
snippet: string;
|
|
24
|
+
};
|
|
25
|
+
skippedReason?: string;
|
|
26
|
+
}
|
|
27
|
+
export declare function installBrainHooks(cwd?: string): HookInstallResult;
|
|
28
|
+
//# sourceMappingURL=gitHooks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gitHooks.d.ts","sourceRoot":"","sources":["../../../src/cli/gitHooks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAmBH,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,oGAAoG;IACpG,MAAM,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAC/C,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAcD,wBAAgB,iBAAiB,CAAC,GAAG,GAAE,MAAsB,GAAG,iBAAiB,CA0DhF"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Installs the git hooks that feed the Brain without anyone remembering to: post-commit ingests
|
|
3
|
+
* the new commit, post-merge/post-checkout pull the team's shared memories (`.lemma-brain/`).
|
|
4
|
+
*
|
|
5
|
+
* `lemma-brain-ingest` shipped with a post-commit recipe in its header comment and nothing ever
|
|
6
|
+
* installed it — on the author's own machine the corpus held 0 commit memories. This is the
|
|
7
|
+
* missing install step, with the constraints a hook installer owes the repo it touches:
|
|
8
|
+
*
|
|
9
|
+
* - Append, never overwrite. An existing hook keeps running exactly as before; Lemma's part is
|
|
10
|
+
* one delimited block, and re-running init replaces that block in place (idempotent).
|
|
11
|
+
* - Never break a commit. Every line is guarded by `command -v`, backgrounded, and silenced.
|
|
12
|
+
* - Respect `core.hooksPath`. `git rev-parse --git-path hooks` resolves it; when it points inside
|
|
13
|
+
* the working tree (husky and friends — versioned hooks), the files are the team's, so they are
|
|
14
|
+
* left alone and the caller gets the lines to add instead.
|
|
15
|
+
*/
|
|
16
|
+
import fs from 'fs';
|
|
17
|
+
import path from 'path';
|
|
18
|
+
import { execFileSync } from 'child_process';
|
|
19
|
+
const BEGIN = '# >>> lemma brain hooks >>>';
|
|
20
|
+
const END = '# <<< lemma brain hooks <<<';
|
|
21
|
+
const HOOK_BODIES = {
|
|
22
|
+
'post-commit': 'command -v lemma-brain-ingest >/dev/null 2>&1 && (lemma-brain-ingest "$(git rev-parse --show-toplevel)" >/dev/null 2>&1 &)',
|
|
23
|
+
'post-merge': 'command -v lemma-brain-ingest >/dev/null 2>&1 && (lemma-brain-ingest "$(git rev-parse --show-toplevel)" --team-pull --no-commits >/dev/null 2>&1 &)',
|
|
24
|
+
// $3 is 1 for a branch checkout, 0 for a file checkout — only a branch switch can change the shared set.
|
|
25
|
+
'post-checkout': '[ "$3" = "1" ] && command -v lemma-brain-ingest >/dev/null 2>&1 && (lemma-brain-ingest "$(git rev-parse --show-toplevel)" --team-pull --no-commits >/dev/null 2>&1 &)',
|
|
26
|
+
};
|
|
27
|
+
function git(cwd, args) {
|
|
28
|
+
try {
|
|
29
|
+
return execFileSync('git', ['-C', cwd, ...args], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function block(hook) {
|
|
36
|
+
return `${BEGIN}\n${HOOK_BODIES[hook]}\n${END}\n`;
|
|
37
|
+
}
|
|
38
|
+
export function installBrainHooks(cwd = process.cwd()) {
|
|
39
|
+
const result = { installed: [], updated: [], unchanged: [] };
|
|
40
|
+
const top = git(cwd, ['rev-parse', '--show-toplevel']);
|
|
41
|
+
const hooksRel = git(cwd, ['rev-parse', '--git-path', 'hooks']);
|
|
42
|
+
if (!top || !hooksRel) {
|
|
43
|
+
result.skippedReason = 'not a git repository';
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
const hooksDir = path.resolve(cwd, hooksRel);
|
|
47
|
+
// Inside the working tree and not under .git → a versioned hooks dir (husky etc.). Compared
|
|
48
|
+
// on realpaths: git reports the toplevel resolved (/private/var/… on macOS) while hooksDir is
|
|
49
|
+
// built from the cwd as given (/var/…), and the two would otherwise look unrelated.
|
|
50
|
+
const real = (p) => { try {
|
|
51
|
+
return fs.realpathSync(p);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return p;
|
|
55
|
+
} };
|
|
56
|
+
const rel = path.relative(real(top), real(hooksDir));
|
|
57
|
+
const insideTree = !rel.startsWith('..') && !path.isAbsolute(rel);
|
|
58
|
+
const underGitDir = rel === '.git' || rel.startsWith(`.git${path.sep}`);
|
|
59
|
+
if (insideTree && !underGitDir) {
|
|
60
|
+
result.manual = {
|
|
61
|
+
hooksDir: rel,
|
|
62
|
+
snippet: Object.keys(HOOK_BODIES).map((h) => `# ${rel}/${h}\n${HOOK_BODIES[h]}`).join('\n\n'),
|
|
63
|
+
};
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
fs.mkdirSync(hooksDir, { recursive: true });
|
|
67
|
+
for (const hook of Object.keys(HOOK_BODIES)) {
|
|
68
|
+
const file = path.join(hooksDir, hook);
|
|
69
|
+
let current = null;
|
|
70
|
+
try {
|
|
71
|
+
current = fs.readFileSync(file, 'utf8');
|
|
72
|
+
}
|
|
73
|
+
catch { /* no hook yet */ }
|
|
74
|
+
let next;
|
|
75
|
+
if (current === null) {
|
|
76
|
+
next = `#!/bin/sh\n${block(hook)}`;
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
const start = current.indexOf(BEGIN);
|
|
80
|
+
const end = current.indexOf(END);
|
|
81
|
+
if (start !== -1 && end > start) {
|
|
82
|
+
next = current.slice(0, start) + block(hook) + current.slice(end + END.length).replace(/^\n/, '');
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
// Appended after the existing script. A hook that ends in `exit 0` would never reach
|
|
86
|
+
// it — insert before a trailing bare exit instead of after it.
|
|
87
|
+
const trailingExit = current.match(/\n(exit\s+\d+\s*)$/);
|
|
88
|
+
next = trailingExit
|
|
89
|
+
? current.slice(0, trailingExit.index + 1) + block(hook) + trailingExit[1]
|
|
90
|
+
: `${current}${current.endsWith('\n') ? '' : '\n'}${block(hook)}`;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (next === current) {
|
|
94
|
+
result.unchanged.push(hook);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
fs.writeFileSync(file, next, 'utf8');
|
|
98
|
+
try {
|
|
99
|
+
fs.chmodSync(file, 0o755);
|
|
100
|
+
}
|
|
101
|
+
catch { /* Windows: git for Windows runs hooks via sh regardless */ }
|
|
102
|
+
(current === null ? result.installed : result.updated).push(hook);
|
|
103
|
+
}
|
|
104
|
+
return result;
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=gitHooks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gitHooks.js","sourceRoot":"","sources":["../../../src/cli/gitHooks.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lemma-proxy.d.ts","sourceRoot":"","sources":["../../../src/cli/lemma-proxy.ts"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"lemma-proxy.d.ts","sourceRoot":"","sources":["../../../src/cli/lemma-proxy.ts"],"names":[],"mappings":";AAgdA,wBAAgB,+BAA+B,CAAC,OAAO,CAAC,EAAE;IAAE,SAAS,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CA8DvF;AAqfD,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAE1C,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAGtD;AAgtDD,wBAAsB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CA8KhL"}
|
|
@@ -48,6 +48,8 @@ import { ProjectRegistry } from '../proxy/ProjectRegistry.js';
|
|
|
48
48
|
import { shouldInject, buildMinimalContext, buildDeepContext, sniffProjectFromMessages } from '../proxy/ContextInjector.js';
|
|
49
49
|
import { savingsLedger } from '../utils/SavingsLedger.js';
|
|
50
50
|
import { CACHE_DIR, PID_FILE, PORT_FILE, STATS_FILE, USAGE_FILE, LICENSE_FILE, CLIPBOARD_PID_FILE, FREE_TIER_LIMIT } from '../proxy/ProjectStore.js';
|
|
51
|
+
import { installBrainHooks } from './gitHooks.js';
|
|
52
|
+
import { getBrain } from '../subconscious/TheBrainV2.js';
|
|
51
53
|
const chroma = new ChromaClient({ host: 'localhost', port: 8000 });
|
|
52
54
|
const dummyEmbeddingFunction = { generate: (texts) => Promise.resolve([]) };
|
|
53
55
|
const complexityRouter = new ComplexityRouter();
|
|
@@ -1349,6 +1351,19 @@ class LemmaServer {
|
|
|
1349
1351
|
this.app.get('/api/savings-breakdown', (req, res) => {
|
|
1350
1352
|
res.json(savingsLedger.getSnapshot());
|
|
1351
1353
|
});
|
|
1354
|
+
// Brain corpus stats for the dashboard. The corpus lives on disk and is shared with the
|
|
1355
|
+
// MCP server and git hooks; getStats() re-syncs from disk if another process wrote it.
|
|
1356
|
+
// cacheHits/cacheMisses are per-process session counters — in this daemon they'd read 0
|
|
1357
|
+
// and look like a Brain nobody uses, so they're dropped rather than shown misleadingly.
|
|
1358
|
+
this.app.get('/api/brain/stats', (req, res) => {
|
|
1359
|
+
try {
|
|
1360
|
+
const { cacheHits, cacheMisses, ...stats } = getBrain().getStats();
|
|
1361
|
+
res.json(stats);
|
|
1362
|
+
}
|
|
1363
|
+
catch (e) {
|
|
1364
|
+
res.status(500).json({ error: e.message });
|
|
1365
|
+
}
|
|
1366
|
+
});
|
|
1352
1367
|
// Serve codebase timeline snapshots (AST Time-Travel Telemetry)
|
|
1353
1368
|
this.app.get('/api/project/timeline', (req, res) => {
|
|
1354
1369
|
res.json({ success: true, timeline: this.timeline });
|
|
@@ -2494,6 +2509,7 @@ program.command('init')
|
|
|
2494
2509
|
.description('Initialize Lemma in the current project (auto-discovery)')
|
|
2495
2510
|
.option('--no-configure', 'Skip automatic configuration of editors, shell overrides, and Claude Desktop')
|
|
2496
2511
|
.option('--yes', 'Skip prompts and accept defaults')
|
|
2512
|
+
.option('--no-hooks', 'Skip installing the git hooks that feed the Brain (post-commit, post-merge, post-checkout)')
|
|
2497
2513
|
.action(async (opts) => {
|
|
2498
2514
|
const project = detectProject();
|
|
2499
2515
|
console.log(`\n🛠️ Initializing Lemma for [${project}]...`);
|
|
@@ -2507,6 +2523,16 @@ program.command('init')
|
|
|
2507
2523
|
if (ensureGitIgnore()) {
|
|
2508
2524
|
console.log('✅ Updated .gitignore with Lemma ignore rules');
|
|
2509
2525
|
}
|
|
2526
|
+
if (opts.hooks !== false) {
|
|
2527
|
+
const hooks = installBrainHooks(process.cwd());
|
|
2528
|
+
const changed = [...hooks.installed, ...hooks.updated];
|
|
2529
|
+
if (hooks.manual) {
|
|
2530
|
+
console.log(`⚠️ Git hooks live in versioned ${hooks.manual.hooksDir}/ — not touching them. To feed the Brain, add:\n${hooks.manual.snippet}`);
|
|
2531
|
+
}
|
|
2532
|
+
else if (changed.length > 0) {
|
|
2533
|
+
console.log(`✅ Git hooks feed the Brain (${changed.join(', ')}): commits become memories, merges pull .lemma-brain/`);
|
|
2534
|
+
}
|
|
2535
|
+
}
|
|
2510
2536
|
const envFile = path.join(process.cwd(), '.env');
|
|
2511
2537
|
const lemmaConfig = `\n# Lemma AI Gateway Configuration\nOPENAI_BASE_URL=http://localhost:8081/v1\nLEMMA_PROJECT=${project}\n`;
|
|
2512
2538
|
if (fs.existsSync(envFile)) {
|