@jmtrin/opencode-kevin 0.1.5 → 0.3.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 +108 -33
- package/dist/migrations/003_v02_signal.sql +58 -0
- package/dist/migrations/004_v03_knowledge.sql +138 -0
- package/dist/plugin/CausalChain.d.ts +11 -0
- package/dist/plugin/CausalChain.js +109 -0
- package/dist/plugin/CausalChain.js.map +1 -0
- package/dist/plugin/ContextInjector.d.ts +25 -1
- package/dist/plugin/ContextInjector.js +72 -15
- package/dist/plugin/ContextInjector.js.map +1 -1
- package/dist/plugin/MemoryService.d.ts +110 -3
- package/dist/plugin/MemoryService.js +432 -20
- package/dist/plugin/MemoryService.js.map +1 -1
- package/dist/plugin/Migrate.d.ts +4 -1
- package/dist/plugin/Migrate.js +40 -1
- package/dist/plugin/Migrate.js.map +1 -1
- package/dist/plugin/PatternMiner.d.ts +49 -0
- package/dist/plugin/PatternMiner.js +133 -0
- package/dist/plugin/PatternMiner.js.map +1 -0
- package/dist/plugin/Reflector.d.ts +45 -2
- package/dist/plugin/Reflector.js +134 -12
- package/dist/plugin/Reflector.js.map +1 -1
- package/dist/plugin/Retrospective.d.ts +4 -1
- package/dist/plugin/Retrospective.js +72 -5
- package/dist/plugin/Retrospective.js.map +1 -1
- package/dist/plugin/Store.d.ts +15 -0
- package/dist/plugin/Store.js +15 -0
- package/dist/plugin/Store.js.map +1 -1
- package/dist/plugin/ToolCallObserver.d.ts +4 -1
- package/dist/plugin/ToolCallObserver.js +39 -7
- package/dist/plugin/ToolCallObserver.js.map +1 -1
- package/dist/plugin/fingerprint.d.ts +27 -0
- package/dist/plugin/fingerprint.js +74 -0
- package/dist/plugin/fingerprint.js.map +1 -0
- package/dist/plugin/index.js +213 -15
- package/dist/plugin/index.js.map +1 -1
- package/dist/plugin/kevin_why.d.ts +19 -0
- package/dist/plugin/kevin_why.js +92 -0
- package/dist/plugin/kevin_why.js.map +1 -0
- package/dist/plugin/memory-format.d.ts +2 -0
- package/dist/plugin/memory-format.js +6 -3
- package/dist/plugin/memory-format.js.map +1 -1
- package/dist/plugin/metrics.d.ts +60 -0
- package/dist/plugin/metrics.js +162 -0
- package/dist/plugin/metrics.js.map +1 -0
- package/dist/plugin/okf-export.d.ts +3 -0
- package/dist/plugin/okf-export.js +86 -0
- package/dist/plugin/okf-export.js.map +1 -0
- package/dist/plugin/okf-import.d.ts +73 -0
- package/dist/plugin/okf-import.js +239 -0
- package/dist/plugin/okf-import.js.map +1 -0
- package/dist/plugin/redact.d.ts +1 -0
- package/dist/plugin/redact.js +7 -0
- package/dist/plugin/redact.js.map +1 -1
- package/migrations/003_v02_signal.sql +58 -0
- package/migrations/004_v03_knowledge.sql +138 -0
- package/package.json +48 -48
package/dist/plugin/Migrate.js
CHANGED
|
@@ -1,11 +1,47 @@
|
|
|
1
1
|
import { readFileSync, readdirSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
+
// Built-in post-apply hooks, keyed by migration version. Each hook runs inside
|
|
4
|
+
// the same transaction as the migration's DDL, so a hook failure rolls back the
|
|
5
|
+
// whole migration. Hooks are only invoked when their version is being applied
|
|
6
|
+
// (i.e., not already present in schema_version).
|
|
7
|
+
const DEFAULT_POST_APPLY_HOOKS = {
|
|
8
|
+
// v0.2.0 Signal Quality: defensive backfill of memories.origin for legacy
|
|
9
|
+
// rows. The column is NOT NULL DEFAULT 'agent', so SQLite already populates
|
|
10
|
+
// pre-existing rows with 'agent' on ALTER TABLE. This hook is a belt-and-
|
|
11
|
+
// braces UPDATE that coerces any NULL/empty stragglers (which would only
|
|
12
|
+
// exist if a partial DB skipped the DEFAULT) back to 'agent'.
|
|
13
|
+
"003": (store) => {
|
|
14
|
+
store
|
|
15
|
+
.prepare("UPDATE memories SET origin = 'agent' WHERE origin IS NULL OR origin = ''")
|
|
16
|
+
.run();
|
|
17
|
+
},
|
|
18
|
+
// v0.3.0 Knowledge + Causality: backfill evidence_count and status for
|
|
19
|
+
// legacy rows. Columns have NOT NULL DEFAULT, so SQLite already populates
|
|
20
|
+
// pre-existing rows. This hook is belt-and-braces in case a partial DB
|
|
21
|
+
// skipped the defaults.
|
|
22
|
+
"004": (store) => {
|
|
23
|
+
store
|
|
24
|
+
.prepare("UPDATE memories SET evidence_count = 0 WHERE evidence_count IS NULL")
|
|
25
|
+
.run();
|
|
26
|
+
store
|
|
27
|
+
.prepare("UPDATE memories SET status = 'active' WHERE status IS NULL OR status = ''")
|
|
28
|
+
.run();
|
|
29
|
+
},
|
|
30
|
+
};
|
|
3
31
|
export class Migrate {
|
|
4
32
|
store;
|
|
5
33
|
migrationsDir;
|
|
6
|
-
|
|
34
|
+
postApplyHooks;
|
|
35
|
+
constructor(store, migrationsDir, postApplyHooks) {
|
|
7
36
|
this.store = store;
|
|
8
37
|
this.migrationsDir = migrationsDir;
|
|
38
|
+
this.postApplyHooks = new Map(Object.entries({
|
|
39
|
+
...DEFAULT_POST_APPLY_HOOKS,
|
|
40
|
+
...(postApplyHooks ?? {}),
|
|
41
|
+
}));
|
|
42
|
+
}
|
|
43
|
+
registerPostApply(version, hook) {
|
|
44
|
+
this.postApplyHooks.set(version, hook);
|
|
9
45
|
}
|
|
10
46
|
async run() {
|
|
11
47
|
this.store.exec(`CREATE TABLE IF NOT EXISTS schema_version (
|
|
@@ -25,6 +61,9 @@ export class Migrate {
|
|
|
25
61
|
const sql = readFileSync(join(this.migrationsDir, migration.file), "utf8");
|
|
26
62
|
this.store.transaction(() => {
|
|
27
63
|
this.store.exec(sql);
|
|
64
|
+
const hook = this.postApplyHooks.get(migration.version);
|
|
65
|
+
if (hook)
|
|
66
|
+
hook(this.store);
|
|
28
67
|
insertVersion.run(migration.version);
|
|
29
68
|
});
|
|
30
69
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Migrate.js","sourceRoot":"","sources":["../../plugin/Migrate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACpD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"Migrate.js","sourceRoot":"","sources":["../../plugin/Migrate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { MemoryService } from "./MemoryService.js";
|
|
2
|
+
import type { Store } from "./Store.js";
|
|
3
|
+
import type { Metrics } from "./metrics.js";
|
|
4
|
+
/**
|
|
5
|
+
* v0.2.0 PatternMiner (K2-021, plan §B6.10 / D2-08).
|
|
6
|
+
*
|
|
7
|
+
* Opt-in deterministic pattern miner. Reads recent `tool_calls` for a given
|
|
8
|
+
* project, groups consecutive ordered 2-grams of `(tool_name)` and 3-grams
|
|
9
|
+
* where the *middle* tool was a failure, and when a group reaches the
|
|
10
|
+
* threshold (default `N ≥ 5` distinct sessions), emits a single
|
|
11
|
+
* `type='pattern'`, `origin='pattern'` memory describing the pattern.
|
|
12
|
+
*
|
|
13
|
+
* Default OFF — must be enabled by setting `kevin_settings.patternminer_enabled`
|
|
14
|
+
* to `'1'`. Idempotent via SELECT-before-INSERT keyed on
|
|
15
|
+
* `(project_id, fingerprint, type='pattern', origin='pattern')`. Migration 003's
|
|
16
|
+
* partial UNIQUE index only covers `type='error' AND origin='reflector'`, so
|
|
17
|
+
* pattern memories cannot rely on a database uniqueness constraint — the
|
|
18
|
+
* SELECT check inside `mine()` is the single idempotency mechanism.
|
|
19
|
+
*
|
|
20
|
+
* NO LLM hop (D2-08). The emitted suggestion is a deterministic template built
|
|
21
|
+
* from the captured tool names.
|
|
22
|
+
*/
|
|
23
|
+
export interface PatternMinerOptions {
|
|
24
|
+
/** Minimum distinct sessions a pattern must appear in before emission.
|
|
25
|
+
* Default 5 (D2-08). */
|
|
26
|
+
threshold?: number;
|
|
27
|
+
}
|
|
28
|
+
export declare class PatternMiner {
|
|
29
|
+
private readonly store;
|
|
30
|
+
private readonly memoryService;
|
|
31
|
+
private readonly metrics;
|
|
32
|
+
private readonly threshold;
|
|
33
|
+
constructor(store: Store, memoryService: MemoryService, metrics?: Metrics | null, options?: PatternMinerOptions);
|
|
34
|
+
/**
|
|
35
|
+
* Mine patterns observed in `tool_calls` for the given project. Returns the
|
|
36
|
+
* number of NEW `pattern` memories emitted this cycle.
|
|
37
|
+
*
|
|
38
|
+
* When the opt-in flag `kevin_settings.patternminer_enabled` is not set to
|
|
39
|
+
* `'1'` (default), this is a no-op and returns 0.
|
|
40
|
+
*
|
|
41
|
+
* When `projectId` is null/undefined, mines tool_calls whose `project_id`
|
|
42
|
+
* IS NULL (legacy / opt-out flow). When `projectId` is a string, mines
|
|
43
|
+
* tool_calls scoped to that project only.
|
|
44
|
+
*/
|
|
45
|
+
mine(projectId?: string | null): number;
|
|
46
|
+
private isEnabled;
|
|
47
|
+
private fetchToolCalls;
|
|
48
|
+
private collectCandidates;
|
|
49
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { fingerprint as computeFingerprint } from "./fingerprint.js";
|
|
2
|
+
const DEFAULT_THRESHOLD = 5;
|
|
3
|
+
const SETTING_KEY = "patternminer_enabled";
|
|
4
|
+
export class PatternMiner {
|
|
5
|
+
store;
|
|
6
|
+
memoryService;
|
|
7
|
+
metrics;
|
|
8
|
+
threshold;
|
|
9
|
+
constructor(store, memoryService, metrics, options) {
|
|
10
|
+
this.store = store;
|
|
11
|
+
this.memoryService = memoryService;
|
|
12
|
+
this.metrics = metrics ?? null;
|
|
13
|
+
this.threshold = options?.threshold ?? DEFAULT_THRESHOLD;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Mine patterns observed in `tool_calls` for the given project. Returns the
|
|
17
|
+
* number of NEW `pattern` memories emitted this cycle.
|
|
18
|
+
*
|
|
19
|
+
* When the opt-in flag `kevin_settings.patternminer_enabled` is not set to
|
|
20
|
+
* `'1'` (default), this is a no-op and returns 0.
|
|
21
|
+
*
|
|
22
|
+
* When `projectId` is null/undefined, mines tool_calls whose `project_id`
|
|
23
|
+
* IS NULL (legacy / opt-out flow). When `projectId` is a string, mines
|
|
24
|
+
* tool_calls scoped to that project only.
|
|
25
|
+
*/
|
|
26
|
+
mine(projectId) {
|
|
27
|
+
if (!this.isEnabled())
|
|
28
|
+
return 0;
|
|
29
|
+
const rows = this.fetchToolCalls(projectId ?? null);
|
|
30
|
+
if (rows.length === 0)
|
|
31
|
+
return 0;
|
|
32
|
+
const candidates = this.collectCandidates(rows);
|
|
33
|
+
if (candidates.length === 0)
|
|
34
|
+
return 0;
|
|
35
|
+
let emitted = 0;
|
|
36
|
+
for (const c of candidates) {
|
|
37
|
+
if (c.sessions.size < this.threshold)
|
|
38
|
+
continue;
|
|
39
|
+
const fp = computeFingerprint(c.content, projectId ?? undefined);
|
|
40
|
+
// Idempotency: migration 003's partial unique only covers
|
|
41
|
+
// (type='error', origin='reflector'). For pattern memories we
|
|
42
|
+
// SELECT to detect a prior emission with the same
|
|
43
|
+
// (project_id, fingerprint, type='pattern', origin='pattern').
|
|
44
|
+
const existing = this.store
|
|
45
|
+
.prepare(`SELECT id FROM memories
|
|
46
|
+
WHERE type = 'pattern' AND origin = 'pattern'
|
|
47
|
+
AND fingerprint = ?
|
|
48
|
+
AND (project_id IS ? OR (project_id IS NULL AND ? IS NULL))
|
|
49
|
+
LIMIT 1`)
|
|
50
|
+
.get(fp, projectId ?? null, projectId ?? null);
|
|
51
|
+
if (existing)
|
|
52
|
+
continue;
|
|
53
|
+
this.memoryService.save({
|
|
54
|
+
type: "pattern",
|
|
55
|
+
origin: "pattern",
|
|
56
|
+
fingerprint: fp,
|
|
57
|
+
content: c.content,
|
|
58
|
+
scope: "project",
|
|
59
|
+
projectId: projectId ?? undefined,
|
|
60
|
+
relevanceScore: 0.5,
|
|
61
|
+
sourceTool: "PatternMiner",
|
|
62
|
+
});
|
|
63
|
+
this.metrics?.incr("patterns_mined", 1);
|
|
64
|
+
emitted += 1;
|
|
65
|
+
}
|
|
66
|
+
return emitted;
|
|
67
|
+
}
|
|
68
|
+
isEnabled() {
|
|
69
|
+
const row = this.store
|
|
70
|
+
.prepare("SELECT value FROM kevin_settings WHERE key = ?")
|
|
71
|
+
.get(SETTING_KEY);
|
|
72
|
+
return row?.value === "1";
|
|
73
|
+
}
|
|
74
|
+
fetchToolCalls(projectId) {
|
|
75
|
+
const nullPid = projectId === null || projectId === undefined;
|
|
76
|
+
const sql = nullPid
|
|
77
|
+
? `SELECT id, session_id, ts, tool, success FROM tool_calls
|
|
78
|
+
WHERE project_id IS NULL
|
|
79
|
+
ORDER BY session_id ASC, ts ASC`
|
|
80
|
+
: `SELECT id, session_id, ts, tool, success FROM tool_calls
|
|
81
|
+
WHERE project_id = ?
|
|
82
|
+
ORDER BY session_id ASC, ts ASC`;
|
|
83
|
+
const stmt = this.store.prepare(sql);
|
|
84
|
+
const rows = (nullPid ? stmt.all() : stmt.all(projectId));
|
|
85
|
+
return rows;
|
|
86
|
+
}
|
|
87
|
+
collectCandidates(rows) {
|
|
88
|
+
// Group rows by session_id preserving arrival order. The SQL already
|
|
89
|
+
// orders by (session_id ASC, ts ASC), so a sequential scan yields each
|
|
90
|
+
// session's tool_calls in execution order.
|
|
91
|
+
const bySession = new Map();
|
|
92
|
+
for (const r of rows) {
|
|
93
|
+
let list = bySession.get(r.session_id);
|
|
94
|
+
if (!list) {
|
|
95
|
+
list = [];
|
|
96
|
+
bySession.set(r.session_id, list);
|
|
97
|
+
}
|
|
98
|
+
list.push(r);
|
|
99
|
+
}
|
|
100
|
+
// 2-grams: ordered pair (a, b) of consecutive tool names per session.
|
|
101
|
+
// 3-grams: ordered triple (a, b, c) where the middle tool b failed
|
|
102
|
+
// (success = 0). These capture the "X→Y fails then Z" lifecycle that
|
|
103
|
+
// the plan §B6.10 calls out.
|
|
104
|
+
const map = new Map();
|
|
105
|
+
const record = (key, content, sessionId) => {
|
|
106
|
+
let cand = map.get(key);
|
|
107
|
+
if (!cand) {
|
|
108
|
+
cand = { key, content, sessions: new Set() };
|
|
109
|
+
map.set(key, cand);
|
|
110
|
+
}
|
|
111
|
+
cand.sessions.add(sessionId);
|
|
112
|
+
};
|
|
113
|
+
for (const [sessionId, list] of bySession) {
|
|
114
|
+
for (let i = 0; i < list.length - 1; i++) {
|
|
115
|
+
const a = list[i];
|
|
116
|
+
const b = list[i + 1];
|
|
117
|
+
const key2 = `2g::${a.tool}::${b.tool}`;
|
|
118
|
+
const content2 = `Pattern: tool "${a.tool}" followed by tool "${b.tool}". Review the ${a.tool}→${b.tool} contract before retrying.`;
|
|
119
|
+
record(key2, content2, sessionId);
|
|
120
|
+
if (i + 2 < list.length) {
|
|
121
|
+
const c = list[i + 2];
|
|
122
|
+
if (b.success === 0) {
|
|
123
|
+
const key3 = `3g::${a.tool}::${b.tool}::${c.tool}`;
|
|
124
|
+
const content3 = `Pattern: tool "${a.tool}" followed by failing tool "${b.tool}" then tool "${c.tool}". Review the ${a.tool}→${b.tool}(failed)→${c.tool} contract before retrying.`;
|
|
125
|
+
record(key3, content3, sessionId);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return Array.from(map.values());
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=PatternMiner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PatternMiner.js","sourceRoot":"","sources":["../../plugin/PatternMiner.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { MemoryService } from "./MemoryService.js";
|
|
2
|
+
import type { Metrics } from "./metrics.js";
|
|
2
3
|
export interface ReflectionInput {
|
|
3
4
|
toolName: string;
|
|
4
5
|
argsSummary: string;
|
|
@@ -7,24 +8,66 @@ export interface ReflectionInput {
|
|
|
7
8
|
exitCode?: number;
|
|
8
9
|
errorType: string;
|
|
9
10
|
sessionId: string;
|
|
11
|
+
/** Optional project scope for v0.2.0 dedup + per-fingerprint throttle. */
|
|
12
|
+
projectId?: string | null;
|
|
13
|
+
/** v0.3.0 fix — callID of the failing tool_call. Used by onLinkError
|
|
14
|
+
* (see ReflectorOptions) to stamp tool_calls.error_fingerprint so the
|
|
15
|
+
* feedback loop can match recurrences by the SAME identity dimension
|
|
16
|
+
* the error memory uses. */
|
|
17
|
+
callID?: string;
|
|
10
18
|
}
|
|
11
19
|
export interface ReflectorOptions {
|
|
12
20
|
throttleMs?: number;
|
|
21
|
+
/** v0.3.0 (K3-018) — optional LLM enrichment callback. Default no-op. */
|
|
22
|
+
enrich?: (lesson: string, stderr: string, stdout: string) => Promise<string | null>;
|
|
23
|
+
/** v0.3.0 fix — link a failing tool_call to the stderr-based fingerprint
|
|
24
|
+
* the matching error memory uses. Default no-op. The caller (index.ts)
|
|
25
|
+
* provides an implementation that UPDATEs tool_calls.error_fingerprint
|
|
26
|
+
* for the given callID, so boost/penalize feedback queries stop missing
|
|
27
|
+
* recurrences by fingerprint mismatch. */
|
|
28
|
+
onLinkError?: (callID: string, fingerprint: string) => void;
|
|
13
29
|
}
|
|
14
30
|
export interface HeuristicLessonInput {
|
|
15
31
|
toolName: string;
|
|
16
32
|
errorType: string;
|
|
17
33
|
firstErrorLine: string;
|
|
34
|
+
/** v0.2.0 (K2-018) — optional pre-computed dispatch result. When absent,
|
|
35
|
+
* dispatch is performed on `firstErrorLine` alone. */
|
|
36
|
+
dispatched?: DispatchedLesson | null;
|
|
37
|
+
}
|
|
38
|
+
/** Result of the per-error-code deterministic rule dispatch (K2-018 / D2-09). */
|
|
39
|
+
export interface DispatchedLesson {
|
|
40
|
+
/** Stable short code captured from the source output (e.g. `TS2304`,
|
|
41
|
+
* `EADDRINUSE`, `F401`, the captured `Error: <Name>` class, the failing
|
|
42
|
+
* command string). `null` when no rule matches (fallback path). */
|
|
43
|
+
code: string | null;
|
|
44
|
+
/** Short deterministic hint, e.g. `import or typo`, `review syscall:
|
|
45
|
+
* EADDRINUSE`. `null` when `code` is null (the v0.1.x fallback path). */
|
|
46
|
+
hint: string | null;
|
|
18
47
|
}
|
|
19
48
|
export declare const ERROR_LINE_RE: RegExp;
|
|
20
49
|
export declare const STRONG_ERROR_RE: RegExp;
|
|
50
|
+
export declare const TS_CODE_RULES: Record<string, string>;
|
|
21
51
|
export declare class Reflector {
|
|
22
52
|
private memoryService;
|
|
23
|
-
private
|
|
53
|
+
private lastReflectionByFp;
|
|
24
54
|
private throttleMs;
|
|
25
|
-
|
|
55
|
+
private metrics;
|
|
56
|
+
private enrichFn;
|
|
57
|
+
private onLinkErrorFn;
|
|
58
|
+
constructor(memoryService: MemoryService, options?: ReflectorOptions, metrics?: Metrics | null);
|
|
26
59
|
invoke(input: ReflectionInput): Promise<string | null>;
|
|
27
60
|
generateHeuristicLesson(input: HeuristicLessonInput): string;
|
|
61
|
+
/**
|
|
62
|
+
* v0.2.0 (K2-018 / D2-09) — deterministic per-error-code rule dispatch.
|
|
63
|
+
* Pure regex sweep over `stderr + '\n' + stdout` with NO LLM hop. Returns
|
|
64
|
+
* a short stable `code` + `hint` pair when a known rule matches, or
|
|
65
|
+
* `{ code: null, hint: null }` for the v0.1.x fallback path (which keeps
|
|
66
|
+
* the v0.1.x `SUGGESTIONS[errorType]` suggestion verbatim).
|
|
67
|
+
*
|
|
68
|
+
* Exported for unit testing (K2-019).
|
|
69
|
+
*/
|
|
70
|
+
dispatchLesson(stderr: string, stdout: string, errorType: string): DispatchedLesson;
|
|
28
71
|
redactPaths(text: string): string;
|
|
29
72
|
redactSecrets(text: string): string;
|
|
30
73
|
private extractFirstErrorLine;
|
package/dist/plugin/Reflector.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { fingerprint as computeFingerprint } from "./fingerprint.js";
|
|
1
2
|
import { redactPaths as redactPathsText } from "./redact.js";
|
|
2
3
|
const DEFAULT_THROTTLE_MS = 60_000;
|
|
3
4
|
const MAX_CONTENT_CHARS = 4096;
|
|
@@ -6,6 +7,12 @@ const TRUNC_SUFFIX = "... [truncated]";
|
|
|
6
7
|
const CONTEXT_PREFIX = "\n\nContext:\n";
|
|
7
8
|
export const ERROR_LINE_RE = /\b(error|failed|fail|cannot find|cannot resolve|TS\d{4,}|exception|traceback|panic|fatal|referenceerror|typeerror|syntaxerror|command failed|non-zero exit)\b/i;
|
|
8
9
|
export const STRONG_ERROR_RE = /\b(cannot find|cannot resolve|TS\d{4,}|error TS\d|command failed|non-zero exit|exit code [1-9]\d*|traceback|referenceerror|typeerror|syntaxerror|fatal error|exception|failed to compile|build failed|compilation failed)\b/i;
|
|
10
|
+
/** v0.1.x fallback table — keyed by `errorType`. RETAINED in v0.2.0 as the
|
|
11
|
+
* fallback for memos whose output does not match a deterministic code rule.
|
|
12
|
+
* The v0.2.0 per-error-code rule table below layers ON TOP of this fallback:
|
|
13
|
+
* when a code is matched, the hint is appended to the v0.1.x suggestion as a
|
|
14
|
+
* 'Likely cause:' line; when no code is matched, output is identical to v0.1.x.
|
|
15
|
+
*/
|
|
9
16
|
const SUGGESTIONS = {
|
|
10
17
|
typecheck: "Verify types and imports before running.",
|
|
11
18
|
lint: "Run linter and fix warnings before committing.",
|
|
@@ -14,6 +21,25 @@ const SUGGESTIONS = {
|
|
|
14
21
|
timeout: "Check for infinite loops or long-running operations.",
|
|
15
22
|
unknown: "Review the error output for details.",
|
|
16
23
|
};
|
|
24
|
+
// --- v0.2.0 (K2-018) lesson v2 — per-error-code deterministic dispatch (D2-09).
|
|
25
|
+
// Pure TS, NO LLM hop. Order of dispatch matches the plan §B6.4 priority list:
|
|
26
|
+
// (1) TS\d{4,5} > (2) Python lint > (3) syscall > (4) generic `Error: <Name>` >
|
|
27
|
+
// (5) `Command "<cmd>" failed` > (6) v0.1.x SUGGESTIONS fallback.
|
|
28
|
+
// v0.2.0 (K2-018) — shared per-error-code rule table. Exported so
|
|
29
|
+
// kevin_why.ts reuses the SAME hints instead of duplicating them
|
|
30
|
+
// (bug #6).
|
|
31
|
+
export const TS_CODE_RULES = {
|
|
32
|
+
"2304": "import or typo",
|
|
33
|
+
"2322": "type mismatch",
|
|
34
|
+
"2740": "missing or wrong property",
|
|
35
|
+
"2552": "undefined identifier",
|
|
36
|
+
"18047": "possibly null",
|
|
37
|
+
};
|
|
38
|
+
const TS_CODE_RE = /\bTS(\d{4,5})\b/;
|
|
39
|
+
const PY_LINT_RE = /\b(ELIF\d{0,4})\b|\b(F\d{3,4})\b|flake8:\s+(\S+)/;
|
|
40
|
+
const SYSCALL_RE = /\b(EADDRINUSE|ENOENT|EACCES|EPERM)\b/;
|
|
41
|
+
const GENERIC_ERROR_RE = /\bError:\s+(\w+)/;
|
|
42
|
+
const COMMAND_FAILED_RE = /Command\s+"([^"]+)"\s+failed/;
|
|
17
43
|
const SECRET_PATTERNS = [
|
|
18
44
|
/(API_KEY|SECRET|PASSWORD|TOKEN)\s*[=:]\s*\S+/gi,
|
|
19
45
|
/\bBearer\s+\S+/gi,
|
|
@@ -23,18 +49,20 @@ const SECRET_VALUE_PATTERN = /\s*=\s*\S+(.*)$/;
|
|
|
23
49
|
const PATH_PATTERNS_DEPRECATED = null;
|
|
24
50
|
export class Reflector {
|
|
25
51
|
memoryService;
|
|
26
|
-
|
|
52
|
+
lastReflectionByFp = new Map();
|
|
27
53
|
throttleMs;
|
|
28
|
-
|
|
54
|
+
metrics;
|
|
55
|
+
enrichFn;
|
|
56
|
+
onLinkErrorFn;
|
|
57
|
+
constructor(memoryService, options, metrics) {
|
|
29
58
|
this.memoryService = memoryService;
|
|
30
59
|
this.throttleMs = options?.throttleMs ?? DEFAULT_THROTTLE_MS;
|
|
60
|
+
this.metrics = metrics ?? null;
|
|
61
|
+
this.enrichFn = options?.enrich ?? (async () => null);
|
|
62
|
+
this.onLinkErrorFn = options?.onLinkError ?? (() => { });
|
|
31
63
|
}
|
|
32
64
|
async invoke(input) {
|
|
33
65
|
const now = Date.now();
|
|
34
|
-
if (now - this.lastReflectionTs < this.throttleMs) {
|
|
35
|
-
return null;
|
|
36
|
-
}
|
|
37
|
-
this.lastReflectionTs = now;
|
|
38
66
|
const redactedStderr = this.redactSecrets(this.redactPaths(input.stderr));
|
|
39
67
|
const redactedStdout = this.redactSecrets(this.redactPaths(input.stdout));
|
|
40
68
|
const sourceOutput = redactedStderr.length > 0 ? redactedStderr : redactedStdout;
|
|
@@ -43,27 +71,60 @@ export class Reflector {
|
|
|
43
71
|
toolName: input.toolName,
|
|
44
72
|
errorType: input.errorType,
|
|
45
73
|
firstErrorLine,
|
|
74
|
+
dispatched: this.dispatchLesson(redactedStderr, redactedStdout, input.errorType),
|
|
46
75
|
});
|
|
76
|
+
// v0.3.0 fix (bug #5) — the per-fingerprint throttle check runs
|
|
77
|
+
// BEFORE the optional LLM enrichment, so throttled repeats never
|
|
78
|
+
// waste an LLM call. The fingerprint is computed from the source
|
|
79
|
+
// output when present, otherwise from the PRE-enrichment lesson,
|
|
80
|
+
// keeping the identity stable and identical to the saved memory's.
|
|
81
|
+
const projectId = input.projectId ?? null;
|
|
82
|
+
const fpContent = sourceOutput.length > 0 ? sourceOutput : lesson;
|
|
83
|
+
const fp = computeFingerprint(fpContent, projectId ?? undefined);
|
|
84
|
+
const last = this.lastReflectionByFp.get(fp) ?? 0;
|
|
85
|
+
if (now - last < this.throttleMs) {
|
|
86
|
+
this.metrics?.incr("reflections_throttled", 1);
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
this.lastReflectionByFp.set(fp, now);
|
|
90
|
+
// v0.3.0 (K3-018): optional LLM enrichment opt-in. Runs only when
|
|
91
|
+
// the throttle check passed (bug #5).
|
|
92
|
+
let enrichedLesson = lesson;
|
|
93
|
+
try {
|
|
94
|
+
const enrichment = await this.enrichFn(lesson, redactedStderr, redactedStdout);
|
|
95
|
+
if (enrichment) {
|
|
96
|
+
enrichedLesson = `${lesson}\n${enrichment}`;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
// enrichment failures are non-blocking
|
|
101
|
+
}
|
|
47
102
|
const metadata = {};
|
|
103
|
+
if (input.callID) {
|
|
104
|
+
metadata.origin_call_id = input.callID;
|
|
105
|
+
}
|
|
48
106
|
let finalContent;
|
|
49
107
|
if (sourceOutput.length > 0) {
|
|
50
|
-
const fullLen =
|
|
108
|
+
const fullLen = enrichedLesson.length + CONTEXT_PREFIX.length + sourceOutput.length;
|
|
51
109
|
if (fullLen <= MAX_CONTENT_CHARS) {
|
|
52
|
-
finalContent = `${
|
|
110
|
+
finalContent = `${enrichedLesson}${CONTEXT_PREFIX}${sourceOutput}`;
|
|
53
111
|
}
|
|
54
112
|
else {
|
|
55
113
|
const budget = MAX_CONTENT_CHARS -
|
|
56
|
-
|
|
114
|
+
enrichedLesson.length -
|
|
57
115
|
CONTEXT_PREFIX.length -
|
|
58
116
|
TRUNC_SUFFIX.length;
|
|
59
117
|
const truncated = sourceOutput.slice(0, Math.max(0, budget));
|
|
60
|
-
finalContent = `${
|
|
118
|
+
finalContent = `${enrichedLesson}${CONTEXT_PREFIX}${truncated}${TRUNC_SUFFIX}`;
|
|
61
119
|
metadata.truncated = true;
|
|
62
120
|
}
|
|
63
121
|
}
|
|
64
122
|
else {
|
|
65
|
-
finalContent =
|
|
123
|
+
finalContent = enrichedLesson;
|
|
66
124
|
}
|
|
125
|
+
// K2-007: the per-fingerprint throttle check ran above (before
|
|
126
|
+
// enrichment); `fp` is reused here so dedup/throttle and the saved
|
|
127
|
+
// memory agree on identity.
|
|
67
128
|
const id = this.memoryService.save({
|
|
68
129
|
type: "error",
|
|
69
130
|
content: finalContent,
|
|
@@ -71,15 +132,76 @@ export class Reflector {
|
|
|
71
132
|
sourceTool: input.toolName,
|
|
72
133
|
sourceSession: input.sessionId,
|
|
73
134
|
metadata,
|
|
135
|
+
origin: "reflector",
|
|
136
|
+
projectId: projectId ?? undefined,
|
|
137
|
+
fingerprint: fp,
|
|
74
138
|
});
|
|
139
|
+
// v0.3.0 fix — stamp the failing tool_call with the stderr-based
|
|
140
|
+
// fingerprint so the feedback loop's recurrence queries can match
|
|
141
|
+
// it (closes the fingerprint-mismatch bug). Fires even after the
|
|
142
|
+
// dedup path returned an existing memory id, since the current
|
|
143
|
+
// call IS a new occurrence of the same error.
|
|
144
|
+
if (input.callID && this.onLinkErrorFn) {
|
|
145
|
+
try {
|
|
146
|
+
this.onLinkErrorFn(input.callID, fp);
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
// linking failure is non-blocking
|
|
150
|
+
}
|
|
151
|
+
}
|
|
75
152
|
return id;
|
|
76
153
|
}
|
|
77
154
|
generateHeuristicLesson(input) {
|
|
155
|
+
const dispatched = input.dispatched ??
|
|
156
|
+
this.dispatchLesson(input.firstErrorLine, "", input.errorType);
|
|
78
157
|
const suggestion = SUGGESTIONS[input.errorType] ?? SUGGESTIONS.unknown;
|
|
79
158
|
const line = input.firstErrorLine.length > MAX_ERROR_LINE_CHARS
|
|
80
159
|
? `${input.firstErrorLine.slice(0, MAX_ERROR_LINE_CHARS)}...`
|
|
81
160
|
: input.firstErrorLine;
|
|
82
|
-
|
|
161
|
+
let lesson = `When ${input.toolName} fails with ${input.errorType}: ${line}\nSuggestion: ${suggestion}`;
|
|
162
|
+
if (dispatched.code && dispatched.hint) {
|
|
163
|
+
lesson += `\nLikely cause: ${dispatched.hint} (code ${dispatched.code})`;
|
|
164
|
+
}
|
|
165
|
+
return lesson;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* v0.2.0 (K2-018 / D2-09) — deterministic per-error-code rule dispatch.
|
|
169
|
+
* Pure regex sweep over `stderr + '\n' + stdout` with NO LLM hop. Returns
|
|
170
|
+
* a short stable `code` + `hint` pair when a known rule matches, or
|
|
171
|
+
* `{ code: null, hint: null }` for the v0.1.x fallback path (which keeps
|
|
172
|
+
* the v0.1.x `SUGGESTIONS[errorType]` suggestion verbatim).
|
|
173
|
+
*
|
|
174
|
+
* Exported for unit testing (K2-019).
|
|
175
|
+
*/
|
|
176
|
+
dispatchLesson(stderr, stdout, errorType) {
|
|
177
|
+
const combined = `${stderr}\n${stdout}`;
|
|
178
|
+
const tsMatch = combined.match(TS_CODE_RE);
|
|
179
|
+
if (tsMatch) {
|
|
180
|
+
const num = tsMatch[1];
|
|
181
|
+
const hint = TS_CODE_RULES[num] ?? `review TS${num}`;
|
|
182
|
+
return { code: `TS${num}`, hint };
|
|
183
|
+
}
|
|
184
|
+
const pyMatch = combined.match(PY_LINT_RE);
|
|
185
|
+
if (pyMatch) {
|
|
186
|
+
const rule = pyMatch[1] || pyMatch[2] || pyMatch[3] || "unknown";
|
|
187
|
+
return { code: rule, hint: `review python lint: ${rule}` };
|
|
188
|
+
}
|
|
189
|
+
const sysMatch = combined.match(SYSCALL_RE);
|
|
190
|
+
if (sysMatch) {
|
|
191
|
+
const code = sysMatch[1];
|
|
192
|
+
return { code, hint: `review syscall: ${code}` };
|
|
193
|
+
}
|
|
194
|
+
const errMatch = combined.match(GENERIC_ERROR_RE);
|
|
195
|
+
if (errMatch) {
|
|
196
|
+
const name = errMatch[1];
|
|
197
|
+
return { code: name, hint: `review error class: ${name}` };
|
|
198
|
+
}
|
|
199
|
+
const cmdMatch = combined.match(COMMAND_FAILED_RE);
|
|
200
|
+
if (cmdMatch) {
|
|
201
|
+
const cmd = cmdMatch[1];
|
|
202
|
+
return { code: cmd, hint: `review failing command: ${cmd}` };
|
|
203
|
+
}
|
|
204
|
+
return { code: null, hint: null };
|
|
83
205
|
}
|
|
84
206
|
redactPaths(text) {
|
|
85
207
|
return redactPathsText(text);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Reflector.js","sourceRoot":"","sources":["../../plugin/Reflector.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,IAAI,eAAe,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"Reflector.js","sourceRoot":"","sources":["../../plugin/Reflector.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { MemoryService } from "./MemoryService.js";
|
|
2
2
|
import type { Store } from "./Store.js";
|
|
3
|
+
import type { Metrics } from "./metrics.js";
|
|
3
4
|
export interface RetrospectiveOptions {
|
|
4
5
|
dir?: string;
|
|
5
6
|
}
|
|
@@ -7,7 +8,9 @@ export declare class Retrospective {
|
|
|
7
8
|
private store;
|
|
8
9
|
private memoryService;
|
|
9
10
|
private retrospectivesDir;
|
|
10
|
-
|
|
11
|
+
private metrics;
|
|
12
|
+
constructor(store: Store, memoryService: MemoryService, options?: RetrospectiveOptions, metrics?: Metrics | null);
|
|
11
13
|
generate(sessionId: string): Promise<string | null>;
|
|
14
|
+
private collectFalsePositives;
|
|
12
15
|
private buildMarkdown;
|
|
13
16
|
}
|