@jmtrin/opencode-kevin 0.9.0 → 1.1.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/LICENSE +21 -0
- package/README.md +575 -823
- package/dist/migrations/001_initial.sql +91 -91
- package/dist/migrations/002_indexes.sql +13 -13
- package/dist/migrations/003_v02_signal.sql +57 -57
- package/dist/migrations/004_v03_knowledge.sql +138 -138
- package/dist/migrations/005_v04_signal.sql +57 -57
- package/dist/migrations/006_v05_glassbox.sql +118 -118
- package/dist/migrations/007_v06_pull.sql +144 -144
- package/dist/migrations/011_v10_proven.sql +69 -0
- package/dist/migrations/012_v11_drift.sql +24 -0
- package/dist/plugin/Archiver.js +2 -18
- package/dist/plugin/ArtifactWriter.d.ts +25 -10
- package/dist/plugin/ArtifactWriter.js +43 -25
- package/dist/plugin/CausalChain.js +32 -14
- package/dist/plugin/ConflictDetector.js +7 -30
- package/dist/plugin/ContextInjector.js +0 -1
- package/dist/plugin/ConventionMiner.js +0 -1
- package/dist/plugin/Curator.js +0 -1
- package/dist/plugin/Feedback.js +2 -20
- package/dist/plugin/HookLiveness.d.ts +7 -0
- package/dist/plugin/HookLiveness.js +26 -28
- package/dist/plugin/InjectionLedger.js +104 -58
- package/dist/plugin/LessonFixer.js +0 -1
- package/dist/plugin/Materializer.js +1 -89
- package/dist/plugin/MemoryService.d.ts +59 -1
- package/dist/plugin/MemoryService.js +13 -107
- package/dist/plugin/Migrate.d.ts +1 -1
- package/dist/plugin/Migrate.js +30 -3
- package/dist/plugin/PatternMiner.js +0 -1
- package/dist/plugin/QualityGate.js +0 -1
- package/dist/plugin/Reflector.js +0 -1
- package/dist/plugin/RepoIdentity.js +0 -1
- package/dist/plugin/RepoTruth.js +0 -1
- package/dist/plugin/Retrospective.js +11 -1
- package/dist/plugin/SharedLayer.js +0 -1
- package/dist/plugin/Store.js +0 -1
- package/dist/plugin/ToolCallObserver.js +18 -6
- package/dist/plugin/capabilities.js +0 -1
- package/dist/plugin/columns.d.ts +11 -0
- package/dist/plugin/columns.js +54 -0
- package/dist/plugin/confidence.js +0 -1
- package/dist/plugin/contract.d.ts +54 -0
- package/dist/plugin/contract.js +383 -0
- package/dist/plugin/diff.js +0 -1
- package/dist/plugin/escape.d.ts +39 -0
- package/dist/plugin/escape.js +60 -0
- package/dist/plugin/fingerprint.js +0 -1
- package/dist/plugin/host.js +0 -1
- package/dist/plugin/index.d.ts +2 -2
- package/dist/plugin/index.js +448 -229
- package/dist/plugin/inferability.js +0 -1
- package/dist/plugin/kevin_approve.js +0 -1
- package/dist/plugin/kevin_audit.d.ts +31 -0
- package/dist/plugin/kevin_audit.js +60 -1
- package/dist/plugin/kevin_bench.d.ts +10 -0
- package/dist/plugin/kevin_bench.js +82 -0
- package/dist/plugin/kevin_conflicts.js +0 -1
- package/dist/plugin/kevin_contract.d.ts +8 -0
- package/dist/plugin/kevin_contract.js +59 -0
- package/dist/plugin/kevin_doctor.d.ts +5 -0
- package/dist/plugin/kevin_doctor.js +30 -2
- package/dist/plugin/kevin_facts.js +0 -1
- package/dist/plugin/kevin_forget.d.ts +33 -0
- package/dist/plugin/kevin_forget.js +260 -0
- package/dist/plugin/kevin_native.js +0 -1
- package/dist/plugin/kevin_propose.js +0 -1
- package/dist/plugin/kevin_publish.js +0 -1
- package/dist/plugin/kevin_why.js +1 -19
- package/dist/plugin/memory-format.js +0 -1
- package/dist/plugin/metrics.d.ts +1 -1
- package/dist/plugin/metrics.js +4 -1
- package/dist/plugin/native.js +0 -1
- package/dist/plugin/okf-export.js +0 -1
- package/dist/plugin/okf-import.js +0 -1
- package/dist/plugin/okf.js +0 -1
- package/dist/plugin/perf.d.ts +35 -0
- package/dist/plugin/perf.js +174 -0
- package/dist/plugin/query-tokenizer.js +56 -9
- package/dist/plugin/redact.js +0 -1
- package/dist/plugin/replay-types.js +0 -1
- package/dist/plugin/replay.js +0 -1
- package/dist/plugin/sqlite-adapter.js +0 -1
- package/dist/plugin/time-ms.d.ts +1 -0
- package/dist/plugin/time-ms.js +16 -0
- package/dist/plugin/uuid.js +0 -1
- package/package.json +22 -6
- package/dist/plugin/Archiver.js.map +0 -1
- package/dist/plugin/ArtifactWriter.js.map +0 -1
- package/dist/plugin/CausalChain.js.map +0 -1
- package/dist/plugin/ConflictDetector.js.map +0 -1
- package/dist/plugin/ContextInjector.js.map +0 -1
- package/dist/plugin/ConventionMiner.js.map +0 -1
- package/dist/plugin/Curator.js.map +0 -1
- package/dist/plugin/Feedback.js.map +0 -1
- package/dist/plugin/HookLiveness.js.map +0 -1
- package/dist/plugin/InjectionLedger.js.map +0 -1
- package/dist/plugin/LessonFixer.js.map +0 -1
- package/dist/plugin/Materializer.js.map +0 -1
- package/dist/plugin/MemoryService.js.map +0 -1
- package/dist/plugin/Migrate.js.map +0 -1
- package/dist/plugin/PatternMiner.js.map +0 -1
- package/dist/plugin/QualityGate.js.map +0 -1
- package/dist/plugin/Reflector.js.map +0 -1
- package/dist/plugin/RepoIdentity.js.map +0 -1
- package/dist/plugin/RepoTruth.js.map +0 -1
- package/dist/plugin/Retrospective.js.map +0 -1
- package/dist/plugin/SharedLayer.js.map +0 -1
- package/dist/plugin/Store.js.map +0 -1
- package/dist/plugin/ToolCallObserver.js.map +0 -1
- package/dist/plugin/capabilities.js.map +0 -1
- package/dist/plugin/confidence.js.map +0 -1
- package/dist/plugin/diff.js.map +0 -1
- package/dist/plugin/fingerprint.js.map +0 -1
- package/dist/plugin/host.js.map +0 -1
- package/dist/plugin/index.js.map +0 -1
- package/dist/plugin/inferability.js.map +0 -1
- package/dist/plugin/kevin_approve.js.map +0 -1
- package/dist/plugin/kevin_audit.js.map +0 -1
- package/dist/plugin/kevin_conflicts.js.map +0 -1
- package/dist/plugin/kevin_doctor.js.map +0 -1
- package/dist/plugin/kevin_facts.js.map +0 -1
- package/dist/plugin/kevin_native.js.map +0 -1
- package/dist/plugin/kevin_propose.js.map +0 -1
- package/dist/plugin/kevin_publish.js.map +0 -1
- package/dist/plugin/kevin_why.js.map +0 -1
- package/dist/plugin/memory-format.js.map +0 -1
- package/dist/plugin/metrics.js.map +0 -1
- package/dist/plugin/native.js.map +0 -1
- package/dist/plugin/okf-export.js.map +0 -1
- package/dist/plugin/okf-import.js.map +0 -1
- package/dist/plugin/okf.js.map +0 -1
- package/dist/plugin/query-tokenizer.js.map +0 -1
- package/dist/plugin/redact.js.map +0 -1
- package/dist/plugin/replay-types.js.map +0 -1
- package/dist/plugin/replay.js.map +0 -1
- package/dist/plugin/sqlite-adapter.js.map +0 -1
- package/dist/plugin/uuid.js.map +0 -1
- package/migrations/001_initial.sql +0 -92
- package/migrations/002_indexes.sql +0 -14
- package/migrations/003_v02_signal.sql +0 -58
- package/migrations/004_v03_knowledge.sql +0 -138
- package/migrations/005_v04_signal.sql +0 -57
- package/migrations/006_v05_glassbox.sql +0 -118
- package/migrations/007_v06_pull.sql +0 -145
- package/migrations/008_v07_truth.sql +0 -124
- package/migrations/009_v08_team.sql +0 -100
- package/migrations/010_v09_native.sql +0 -78
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
-- =============================================================
|
|
2
|
+
-- Kevin v1.0.0 "Proven" — migration 011
|
|
3
|
+
-- Adds: perf sampling, benchmark results.
|
|
4
|
+
-- No ALTER TABLE (second release running); no column is retyped.
|
|
5
|
+
-- =============================================================
|
|
6
|
+
|
|
7
|
+
-- 1. Per-scope latency aggregates, flushed at session.idle.
|
|
8
|
+
-- Machine-scoped: no project_id, for the same reason hook_liveness
|
|
9
|
+
-- has none (v0.9.0 D9-08) — latency is a property of the install.
|
|
10
|
+
CREATE TABLE IF NOT EXISTS perf_samples (
|
|
11
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
12
|
+
scope TEXT NOT NULL,
|
|
13
|
+
sample_count INTEGER NOT NULL,
|
|
14
|
+
p50_ms REAL NOT NULL,
|
|
15
|
+
p95_ms REAL NOT NULL,
|
|
16
|
+
max_ms REAL NOT NULL,
|
|
17
|
+
budget_p95_ms REAL NOT NULL,
|
|
18
|
+
within_budget INTEGER NOT NULL DEFAULT 1,
|
|
19
|
+
recorded_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
CREATE INDEX IF NOT EXISTS idx_perf_samples_scope
|
|
23
|
+
ON perf_samples(scope, recorded_at DESC);
|
|
24
|
+
CREATE INDEX IF NOT EXISTS idx_perf_samples_breach
|
|
25
|
+
ON perf_samples(within_budget) WHERE within_budget = 0;
|
|
26
|
+
|
|
27
|
+
-- 2. Benchmark results. Append-only. One row per arm per run.
|
|
28
|
+
-- arm CHECK is closed by the plan; widening requires a table rebuild (intentional friction).
|
|
29
|
+
-- scope CHECK is intentionally absent: the scope union may gain members in a 1.x minor.
|
|
30
|
+
CREATE TABLE IF NOT EXISTS bench_runs (
|
|
31
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
32
|
+
corpus_digest TEXT NOT NULL,
|
|
33
|
+
contract_digest TEXT NOT NULL,
|
|
34
|
+
package_version TEXT NOT NULL,
|
|
35
|
+
runtime TEXT NOT NULL,
|
|
36
|
+
arm TEXT NOT NULL,
|
|
37
|
+
k INTEGER NOT NULL,
|
|
38
|
+
precision_at_k REAL NOT NULL,
|
|
39
|
+
recall_at_k REAL NOT NULL,
|
|
40
|
+
mrr REAL NOT NULL,
|
|
41
|
+
ran_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
42
|
+
CHECK (arm IN ('none', 'recent-k', 'random-k', 'kevin'))
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
CREATE INDEX IF NOT EXISTS idx_bench_runs_corpus
|
|
46
|
+
ON bench_runs(corpus_digest, ran_at DESC);
|
|
47
|
+
CREATE INDEX IF NOT EXISTS idx_bench_runs_arm
|
|
48
|
+
ON bench_runs(arm, ran_at DESC);
|
|
49
|
+
|
|
50
|
+
-- 3. Metric seeds (45 -> 51).
|
|
51
|
+
INSERT OR IGNORE INTO kevin_metrics (key, value) VALUES ('perf_samples_recorded', 0);
|
|
52
|
+
INSERT OR IGNORE INTO kevin_metrics (key, value) VALUES ('perf_budget_breaches', 0);
|
|
53
|
+
INSERT OR IGNORE INTO kevin_metrics (key, value) VALUES ('dispose_fires_total', 0);
|
|
54
|
+
INSERT OR IGNORE INTO kevin_metrics (key, value) VALUES ('dispose_misses_total', 0);
|
|
55
|
+
INSERT OR IGNORE INTO kevin_metrics (key, value) VALUES ('contract_digest_changes', 0);
|
|
56
|
+
INSERT OR IGNORE INTO kevin_metrics (key, value) VALUES ('bench_runs_total', 0);
|
|
57
|
+
|
|
58
|
+
-- 4. Setting seeds (27 -> 31). All TEXT; '0'/'1' are strings.
|
|
59
|
+
INSERT OR IGNORE INTO kevin_settings (key, value) VALUES ('perf_enabled', '1');
|
|
60
|
+
INSERT OR IGNORE INTO kevin_settings (key, value) VALUES ('perf_ring_capacity', '512');
|
|
61
|
+
INSERT OR IGNORE INTO kevin_settings (key, value) VALUES ('perf_flush_on_idle', '1');
|
|
62
|
+
INSERT OR IGNORE INTO kevin_settings (key, value) VALUES ('contract_report_enabled', '1');
|
|
63
|
+
|
|
64
|
+
-- 5. `dispose` becomes the seventh instrumented hook (v0.9.0 migration 010
|
|
65
|
+
-- seeded six rows; this is a data seed, not a schema change).
|
|
66
|
+
INSERT OR IGNORE INTO hook_liveness (hook) VALUES ('dispose');
|
|
67
|
+
|
|
68
|
+
-- 6. Schema version.
|
|
69
|
+
INSERT OR IGNORE INTO schema_version (version) VALUES ('011');
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
-- millisecond companions for the two tables whose ORDERING decides outcomes.
|
|
2
|
+
ALTER TABLE tool_calls ADD COLUMN ts_ms INTEGER;
|
|
3
|
+
ALTER TABLE kevin_injections ADD COLUMN injected_at_ms INTEGER;
|
|
4
|
+
|
|
5
|
+
-- conservative backfill: seconds -> ms (x1000). Rows keep their original
|
|
6
|
+
-- second-granularity value in the old column; nothing is rewritten there.
|
|
7
|
+
UPDATE tool_calls SET ts_ms =
|
|
8
|
+
CAST(strftime('%s', ts) AS INTEGER) * 1000
|
|
9
|
+
WHERE ts_ms IS NULL AND ts IS NOT NULL;
|
|
10
|
+
|
|
11
|
+
UPDATE kevin_injections SET injected_at_ms =
|
|
12
|
+
CAST(strftime('%s', injected_at) AS INTEGER) * 1000
|
|
13
|
+
WHERE injected_at_ms IS NULL AND injected_at IS NOT NULL;
|
|
14
|
+
|
|
15
|
+
CREATE INDEX idx_tool_calls_ts_ms ON tool_calls(ts_ms);
|
|
16
|
+
CREATE INDEX idx_injections_injected_ms ON kevin_injections(injected_at_ms);
|
|
17
|
+
|
|
18
|
+
-- metric seeds (C-05 additions carry since=1.1.0 in contract)
|
|
19
|
+
INSERT INTO kevin_metrics (key, value, updated_at) VALUES
|
|
20
|
+
('bench_regression_failures', 0, datetime('now')),
|
|
21
|
+
('forget_requests_total', 0, datetime('now')),
|
|
22
|
+
('forget_tombstones_published', 0, datetime('now'));
|
|
23
|
+
|
|
24
|
+
INSERT INTO schema_version (version) VALUES ('012');
|
package/dist/plugin/Archiver.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { hasArchivedColumn } from "./columns.js";
|
|
1
2
|
/**
|
|
2
3
|
* v0.5.0 Archiver (K5-012 / plan §5.4, D5-05).
|
|
3
4
|
*
|
|
@@ -71,23 +72,7 @@ export class Archiver {
|
|
|
71
72
|
}
|
|
72
73
|
}
|
|
73
74
|
hasArchivedColumn() {
|
|
74
|
-
return
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
// v0.6.0 (K6-001a) — positive-only caching: a successful probe is cached,
|
|
78
|
-
// a failed probe is NOT. A Store migrated in place heals on the next call.
|
|
79
|
-
const archivedColumnCache = new WeakMap();
|
|
80
|
-
function hasArchivedColumnCached(store) {
|
|
81
|
-
const cached = archivedColumnCache.get(store);
|
|
82
|
-
if (cached === true)
|
|
83
|
-
return true;
|
|
84
|
-
try {
|
|
85
|
-
store.prepare("SELECT archived_at FROM memories LIMIT 1").get();
|
|
86
|
-
archivedColumnCache.set(store, true);
|
|
87
|
-
return true;
|
|
88
|
-
}
|
|
89
|
-
catch {
|
|
90
|
-
return false;
|
|
75
|
+
return hasArchivedColumn(this.store);
|
|
91
76
|
}
|
|
92
77
|
}
|
|
93
78
|
/** 'YYYY-MM-DD HH:MM:SS' UTC — the format SQLite `datetime('now')` uses,
|
|
@@ -96,4 +81,3 @@ function formatSqliteUtc(d) {
|
|
|
96
81
|
const pad = (n) => String(n).padStart(2, "0");
|
|
97
82
|
return `${d.getUTCFullYear()}-${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())} ${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}:${pad(d.getUTCSeconds())}`;
|
|
98
83
|
}
|
|
99
|
-
//# sourceMappingURL=Archiver.js.map
|
|
@@ -35,18 +35,33 @@ export interface WritePlan {
|
|
|
35
35
|
readonly hashAfter: string;
|
|
36
36
|
}
|
|
37
37
|
/**
|
|
38
|
-
* Rule 9 (
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
38
|
+
* Rule 9, layer (a) — superseded in v1.0.0 by `escapeForMarkerBlock`
|
|
39
|
+
* in `plugin/escape.ts` (K10-027), which carries the same idempotent
|
|
40
|
+
* entity discipline.
|
|
41
|
+
*/
|
|
42
|
+
/**
|
|
43
|
+
* v1.0.0 (K10-027 / plan §5.7) — the boundary functions now live in
|
|
44
|
+
* `plugin/escape.ts`; this composer applies them in fence-then-marker
|
|
45
|
+
* order. Layer (b) — strip any line containing `kevin:begin` or
|
|
46
|
+
* `kevin:end`, in any casing — is a filter rather than an escape and
|
|
47
|
+
* stays here. Without layer (b), a memory containing a literal
|
|
48
|
+
* `<!-- kevin:end -->` line would close the marker comment early and
|
|
49
|
+
* let subsequent content escape the curated region on the next
|
|
50
|
+
* regeneration — a marker-injection variant of the v0.1.5
|
|
51
|
+
* prompt-injection defect (plan §3.5). The trailing `-->` strip is kept
|
|
52
|
+
* as defence in depth although `escapeForMarkerBlock` already escapes
|
|
53
|
+
* every `>` that could form one.
|
|
48
54
|
*/
|
|
49
55
|
export declare function sanitizeArtifactBody(body: string): string;
|
|
56
|
+
/**
|
|
57
|
+
* v1.0.0 (K10-027 / plan §5.7, rule 2) — whole-file writes escape by
|
|
58
|
+
* container. Only `.okf` files are line-oriented JSON: each line gets
|
|
59
|
+
* `escapeForOkfLine`, which is the identity on a well-formed OKF line
|
|
60
|
+
* (canonical JSON never contains raw control characters) and therefore
|
|
61
|
+
* preserves the re-render `noop`. Kevin-owned markdown paths keep their
|
|
62
|
+
* bytes — fences there are legitimate content.
|
|
63
|
+
*/
|
|
64
|
+
export declare function escapeForContainer(path: string, content: string): string;
|
|
50
65
|
/**
|
|
51
66
|
* v0.6.0 (K6-005 / plan §5.1) — the single write path to disk (D6-01).
|
|
52
67
|
*
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { closeSync, fsyncSync, openSync, readFileSync, renameSync, unlinkSync, writeSync, } from "node:fs";
|
|
3
3
|
import { unifiedDiff } from "./diff.js";
|
|
4
|
+
import { escapeForFence, escapeForMarkerBlock, escapeForOkfLine, } from "./escape.js";
|
|
4
5
|
import { uuidv7 } from "./uuid.js";
|
|
5
6
|
// v0.6.0 (K6-005 / plan §5.1, D6-02) — the frozen marker contract. The exact
|
|
6
7
|
// byte sequences are load-bearing: README, v1.0.0 plan C-01 and the round-trip
|
|
@@ -30,39 +31,55 @@ function normalizeEol(body, eol) {
|
|
|
30
31
|
return body.replace(/\r\n/g, "\n").replace(/\n/g, eol);
|
|
31
32
|
}
|
|
32
33
|
/**
|
|
33
|
-
* Rule 9, layer (a) —
|
|
34
|
-
*
|
|
35
|
-
* entity
|
|
36
|
-
* sanitized output is a fixed point. The round-trip property of the marker
|
|
37
|
-
* block depends on this: a non-idempotent escape would grow `&lt;` on
|
|
38
|
-
* every regeneration (K6-009 / plan §5.1 rule 9).
|
|
34
|
+
* Rule 9, layer (a) — superseded in v1.0.0 by `escapeForMarkerBlock`
|
|
35
|
+
* in `plugin/escape.ts` (K10-027), which carries the same idempotent
|
|
36
|
+
* entity discipline.
|
|
39
37
|
*/
|
|
40
|
-
function escapeIdempotent(text) {
|
|
41
|
-
return text
|
|
42
|
-
.replace(/&(?!(amp|lt|gt|#\d+);)/g, "&")
|
|
43
|
-
.replace(/</g, "<")
|
|
44
|
-
.replace(/>/g, ">");
|
|
45
|
-
}
|
|
46
38
|
/**
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
39
|
+
* v1.0.0 (K10-027 / plan §5.7) — the boundary functions now live in
|
|
40
|
+
* `plugin/escape.ts`; this composer applies them in fence-then-marker
|
|
41
|
+
* order. Layer (b) — strip any line containing `kevin:begin` or
|
|
42
|
+
* `kevin:end`, in any casing — is a filter rather than an escape and
|
|
43
|
+
* stays here. Without layer (b), a memory containing a literal
|
|
44
|
+
* `<!-- kevin:end -->` line would close the marker comment early and
|
|
45
|
+
* let subsequent content escape the curated region on the next
|
|
46
|
+
* regeneration — a marker-injection variant of the v0.1.5
|
|
47
|
+
* prompt-injection defect (plan §3.5). The trailing `-->` strip is kept
|
|
48
|
+
* as defence in depth although `escapeForMarkerBlock` already escapes
|
|
49
|
+
* every `>` that could form one.
|
|
57
50
|
*/
|
|
58
51
|
export function sanitizeArtifactBody(body) {
|
|
59
|
-
const escaped =
|
|
52
|
+
const escaped = escapeForMarkerBlock(escapeForFence(body));
|
|
60
53
|
const kept = escaped
|
|
61
54
|
.split("\n")
|
|
62
55
|
.filter((line) => !/kevin:begin|kevin:end/i.test(line))
|
|
63
56
|
.join("\n");
|
|
64
57
|
return kept.replace(/-->/g, "");
|
|
65
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* v1.0.0 (K10-027 / plan §5.7, rule 2) — whole-file writes escape by
|
|
61
|
+
* container. Only `.okf` files are line-oriented JSON: each line gets
|
|
62
|
+
* `escapeForOkfLine`, which is the identity on a well-formed OKF line
|
|
63
|
+
* (canonical JSON never contains raw control characters) and therefore
|
|
64
|
+
* preserves the re-render `noop`. Kevin-owned markdown paths keep their
|
|
65
|
+
* bytes — fences there are legitimate content.
|
|
66
|
+
*/
|
|
67
|
+
export function escapeForContainer(path, content) {
|
|
68
|
+
if (!path.endsWith(".okf"))
|
|
69
|
+
return content;
|
|
70
|
+
return content
|
|
71
|
+
.split("\n")
|
|
72
|
+
.map((line) => {
|
|
73
|
+
// A CRLF file (healHeader preserves the original EOL) leaves a
|
|
74
|
+
// trailing \r on each line after the split — it is line-ending
|
|
75
|
+
// bytes, not statement content, so it must survive untouched.
|
|
76
|
+
if (line.endsWith("\r")) {
|
|
77
|
+
return `${escapeForOkfLine(line.slice(0, -1))}\r`;
|
|
78
|
+
}
|
|
79
|
+
return escapeForOkfLine(line);
|
|
80
|
+
})
|
|
81
|
+
.join("\n");
|
|
82
|
+
}
|
|
66
83
|
/**
|
|
67
84
|
* v0.6.0 (K6-005 / plan §5.1) — the single write path to disk (D6-01).
|
|
68
85
|
*
|
|
@@ -113,7 +130,9 @@ export class ArtifactWriter {
|
|
|
113
130
|
// like any other refusal: after = before, both hashes recorded.
|
|
114
131
|
const refusal = request.refusal;
|
|
115
132
|
const refused = refusal !== undefined;
|
|
116
|
-
|
|
133
|
+
// K10-027 — the container boundary: OKF lines are escaped at the
|
|
134
|
+
// single write path, never by callers.
|
|
135
|
+
const after = refused ? before : escapeForContainer(path, content);
|
|
117
136
|
return {
|
|
118
137
|
path,
|
|
119
138
|
before,
|
|
@@ -270,4 +289,3 @@ export class ArtifactWriter {
|
|
|
270
289
|
.run(uuidv7(), proposalId ?? null, this.projectId, plan.path, Buffer.byteLength(plan.before, "utf8"), Buffer.byteLength(plan.after, "utf8"), plan.hashBefore, plan.hashAfter, plan.outcome, plan.reason ?? null);
|
|
271
290
|
}
|
|
272
291
|
}
|
|
273
|
-
//# sourceMappingURL=ArtifactWriter.js.map
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { extractFixArgs, } from "./LessonFixer.js";
|
|
2
|
+
import { hasColumn } from "./columns.js";
|
|
3
|
+
import { toMs } from "./time-ms.js";
|
|
2
4
|
/** K3-007 — a success only links to a failure within this many calls. */
|
|
3
5
|
const MAX_LINK_DISTANCE = 10;
|
|
4
6
|
export class CausalChain {
|
|
@@ -24,11 +26,15 @@ export class CausalChain {
|
|
|
24
26
|
// distance, so an unrelated success (e.g. an `ls` run after a
|
|
25
27
|
// typecheck failure) was stamped as the fix for that error.
|
|
26
28
|
onSuccess(_tool, _args, _projectId, sessionId) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
// v1.1.0 (K11-004 / plan §5.2, D11-01/D11-07) — ms-aware window: prefer _ms
|
|
30
|
+
const hasTsMs = hasColumn(this.store, "tool_calls", "ts_ms");
|
|
31
|
+
const successRow = (hasTsMs
|
|
32
|
+
? this.store.prepare(`SELECT rowid, tool, args_summary, ts, ts_ms FROM tool_calls
|
|
33
|
+
WHERE session_id = ? AND success = 1
|
|
34
|
+
ORDER BY rowid DESC LIMIT 1`)
|
|
35
|
+
: this.store.prepare(`SELECT rowid, tool, args_summary, ts FROM tool_calls
|
|
36
|
+
WHERE session_id = ? AND success = 1
|
|
37
|
+
ORDER BY rowid DESC LIMIT 1`)).get(sessionId);
|
|
32
38
|
if (!successRow)
|
|
33
39
|
return;
|
|
34
40
|
const linkedFps = new Set(this.store
|
|
@@ -40,20 +46,33 @@ export class CausalChain {
|
|
|
40
46
|
// onLinkError) is the SAME identity dimension the error memory
|
|
41
47
|
// uses; `fingerprint` is the legacy tool|args|success hash and
|
|
42
48
|
// simply never matches a reflector error memory.
|
|
43
|
-
const failRows =
|
|
44
|
-
.prepare(`SELECT rowid, COALESCE(error_fingerprint, fingerprint) AS fp
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
.
|
|
49
|
+
const failRows = (hasTsMs
|
|
50
|
+
? this.store.prepare(`SELECT rowid, COALESCE(error_fingerprint, fingerprint) AS fp, ts, ts_ms
|
|
51
|
+
FROM tool_calls
|
|
52
|
+
WHERE session_id = ?
|
|
53
|
+
AND success = 0
|
|
54
|
+
AND (error_fingerprint IS NOT NULL OR fingerprint IS NOT NULL)
|
|
55
|
+
ORDER BY rowid DESC LIMIT ?`)
|
|
56
|
+
: this.store.prepare(`SELECT rowid, COALESCE(error_fingerprint, fingerprint) AS fp, ts
|
|
57
|
+
FROM tool_calls
|
|
58
|
+
WHERE session_id = ?
|
|
59
|
+
AND success = 0
|
|
60
|
+
AND (error_fingerprint IS NOT NULL OR fingerprint IS NOT NULL)
|
|
61
|
+
ORDER BY rowid DESC LIMIT ?`)).all(sessionId, MAX_LINK_DISTANCE);
|
|
51
62
|
for (const fail of failRows) {
|
|
52
63
|
if (!fail.fp || linkedFps.has(fail.fp))
|
|
53
64
|
continue;
|
|
54
65
|
const dist = successRow.rowid - fail.rowid;
|
|
55
66
|
if (dist <= 0 || dist > MAX_LINK_DISTANCE)
|
|
56
67
|
continue;
|
|
68
|
+
// v1.1.0 — ≤24h window uses ms when available
|
|
69
|
+
const successMs = toMs(successRow.ts, successRow.ts_ms ?? null);
|
|
70
|
+
const failMs = toMs(fail.ts, fail.ts_ms ?? null);
|
|
71
|
+
if (successMs !== null && failMs !== null) {
|
|
72
|
+
const diff = successMs - failMs;
|
|
73
|
+
if (diff < 0 || diff > 86_400_000)
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
57
76
|
const mem = this.store
|
|
58
77
|
.prepare(`SELECT 1 FROM memories
|
|
59
78
|
WHERE fingerprint = ? AND type = 'error'
|
|
@@ -176,4 +195,3 @@ export class CausalChain {
|
|
|
176
195
|
return row?.value === "1";
|
|
177
196
|
}
|
|
178
197
|
}
|
|
179
|
-
//# sourceMappingURL=CausalChain.js.map
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { mapRow } from "./MemoryService.js";
|
|
1
2
|
import { normalize } from "./fingerprint.js";
|
|
3
|
+
import { STOP_WORDS } from "./query-tokenizer.js";
|
|
2
4
|
import { uuidv7 } from "./uuid.js";
|
|
3
5
|
const POLARITY_RULES = [
|
|
4
6
|
{
|
|
@@ -14,33 +16,6 @@ const POLARITY_RULES = [
|
|
|
14
16
|
{ positive: ["enable"], negative: [["disable"]] },
|
|
15
17
|
{ positive: ["prefer"], negative: [["avoid"]] },
|
|
16
18
|
];
|
|
17
|
-
const STOP_WORDS = new Set([
|
|
18
|
-
"a",
|
|
19
|
-
"an",
|
|
20
|
-
"and",
|
|
21
|
-
"are",
|
|
22
|
-
"as",
|
|
23
|
-
"at",
|
|
24
|
-
"be",
|
|
25
|
-
"by",
|
|
26
|
-
"for",
|
|
27
|
-
"from",
|
|
28
|
-
"in",
|
|
29
|
-
"is",
|
|
30
|
-
"it",
|
|
31
|
-
"of",
|
|
32
|
-
"on",
|
|
33
|
-
"or",
|
|
34
|
-
"that",
|
|
35
|
-
"the",
|
|
36
|
-
"this",
|
|
37
|
-
"to",
|
|
38
|
-
"with",
|
|
39
|
-
"we",
|
|
40
|
-
"when",
|
|
41
|
-
"where",
|
|
42
|
-
"you",
|
|
43
|
-
]);
|
|
44
19
|
const POLARITY_WORDS = new Set([
|
|
45
20
|
"use",
|
|
46
21
|
"do",
|
|
@@ -157,9 +132,12 @@ export class ConflictDetector {
|
|
|
157
132
|
FROM memories WHERE project_id = ? AND status = 'active'`)
|
|
158
133
|
.all(this.projectId);
|
|
159
134
|
return rows.flatMap((row) => {
|
|
160
|
-
|
|
135
|
+
// v1.1.0 (K11-014) — route through mapRow so metadata is parsed and confidence derived
|
|
136
|
+
const memory = mapRow(row);
|
|
161
137
|
const reasons = this.repoTruth?.contradictions(memory) ?? [];
|
|
162
|
-
return reasons.length > 0
|
|
138
|
+
return reasons.length > 0
|
|
139
|
+
? [{ memoryId: row.id, reasons }]
|
|
140
|
+
: [];
|
|
163
141
|
});
|
|
164
142
|
}
|
|
165
143
|
detectDecisionPairs() {
|
|
@@ -280,4 +258,3 @@ function oppositePair(a, b) {
|
|
|
280
258
|
}
|
|
281
259
|
return null;
|
|
282
260
|
}
|
|
283
|
-
//# sourceMappingURL=ConflictDetector.js.map
|
package/dist/plugin/Curator.js
CHANGED
package/dist/plugin/Feedback.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { hasFeedbackTable } from "./columns.js";
|
|
1
2
|
import { uuidv7 } from "./uuid.js";
|
|
2
3
|
const POSITIVE_VERDICTS = ["useful"];
|
|
3
4
|
const NEGATIVE_VERDICTS = [
|
|
@@ -5,25 +6,7 @@ const NEGATIVE_VERDICTS = [
|
|
|
5
6
|
"outdated",
|
|
6
7
|
"ignore",
|
|
7
8
|
];
|
|
8
|
-
//
|
|
9
|
-
// descriptive error instead of a bare "no such table" (the tool layer turns
|
|
10
|
-
// it into a graceful message).
|
|
11
|
-
// v0.6.0 (K6-001a) — positive-only caching: a successful probe is cached,
|
|
12
|
-
// a failed probe is NOT. A Store migrated in place heals on the next call.
|
|
13
|
-
const feedbackTableCache = new WeakMap();
|
|
14
|
-
function hasFeedbackTable(store) {
|
|
15
|
-
const cached = feedbackTableCache.get(store);
|
|
16
|
-
if (cached === true)
|
|
17
|
-
return true;
|
|
18
|
-
try {
|
|
19
|
-
store.prepare("SELECT COUNT(*) FROM memory_feedback").get();
|
|
20
|
-
feedbackTableCache.set(store, true);
|
|
21
|
-
return true;
|
|
22
|
-
}
|
|
23
|
-
catch {
|
|
24
|
-
return false;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
9
|
+
// v1.1.0 (K11-011) — table probe delegates to columns registry
|
|
27
10
|
export class Feedback {
|
|
28
11
|
store;
|
|
29
12
|
metrics;
|
|
@@ -135,4 +118,3 @@ export class Feedback {
|
|
|
135
118
|
.run(memoryId, memoryId, memoryId);
|
|
136
119
|
}
|
|
137
120
|
}
|
|
138
|
-
//# sourceMappingURL=Feedback.js.map
|
|
@@ -37,6 +37,7 @@ interface HookLivenessOptions {
|
|
|
37
37
|
export declare class HookLiveness {
|
|
38
38
|
private readonly store;
|
|
39
39
|
private readonly options;
|
|
40
|
+
excessArityCount: number;
|
|
40
41
|
private readonly counters;
|
|
41
42
|
private readonly seenSessions;
|
|
42
43
|
private readonly suppressedSessions;
|
|
@@ -68,6 +69,12 @@ export declare class HookLiveness {
|
|
|
68
69
|
/** Persists every counter row. Called on the metrics.flush() cadence. */
|
|
69
70
|
flush(): void;
|
|
70
71
|
private makeWrapper;
|
|
72
|
+
/**
|
|
73
|
+
* v1.0.0 (K10-013 / plan §5.3) — records the `dispose` fire and
|
|
74
|
+
* flushes immediately, from inside the dispose delegate: this is the
|
|
75
|
+
* last write of the process, and nothing after it can persist.
|
|
76
|
+
*/
|
|
77
|
+
recordDispose(): void;
|
|
71
78
|
private recordSuccess;
|
|
72
79
|
private recordError;
|
|
73
80
|
private loadFromDb;
|
|
@@ -25,6 +25,8 @@ export function parseThreshold(text) {
|
|
|
25
25
|
export class HookLiveness {
|
|
26
26
|
store;
|
|
27
27
|
options;
|
|
28
|
+
// v1.1.0 (K11-015) — debug counter for excess arity; never logged on hot path
|
|
29
|
+
excessArityCount = 0;
|
|
28
30
|
counters;
|
|
29
31
|
seenSessions;
|
|
30
32
|
suppressedSessions = new Set();
|
|
@@ -212,6 +214,12 @@ export class HookLiveness {
|
|
|
212
214
|
}
|
|
213
215
|
makeWrapper(key, delegate) {
|
|
214
216
|
const record = () => {
|
|
217
|
+
// v1.0.0 (K10-013) — dispose is recorded inside its own delegate
|
|
218
|
+
// (via recordDispose()) rather than here: the wrapper's post-return
|
|
219
|
+
// record would land after the store is closed and could never be
|
|
220
|
+
// persisted — the event being detected is the process ending.
|
|
221
|
+
if (key === "dispose")
|
|
222
|
+
return;
|
|
215
223
|
if (HOOK_NAMES.includes(key)) {
|
|
216
224
|
this.recordSuccess(key);
|
|
217
225
|
}
|
|
@@ -222,34 +230,16 @@ export class HookLiveness {
|
|
|
222
230
|
}
|
|
223
231
|
throw e;
|
|
224
232
|
};
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
catch (e) {
|
|
234
|
-
return recordError(e);
|
|
235
|
-
}
|
|
236
|
-
};
|
|
237
|
-
}
|
|
238
|
-
if (n === 1) {
|
|
239
|
-
return async (a) => {
|
|
240
|
-
try {
|
|
241
|
-
const result = await delegate(a);
|
|
242
|
-
record();
|
|
243
|
-
return result;
|
|
244
|
-
}
|
|
245
|
-
catch (e) {
|
|
246
|
-
return recordError(e);
|
|
247
|
-
}
|
|
248
|
-
};
|
|
249
|
-
}
|
|
250
|
-
return async (a, b) => {
|
|
233
|
+
// v1.1.0 (K11-015) — arity guard: maximum supported arity is 2 (plan §5.5).
|
|
234
|
+
// Excess args are sliced and counted via excessArityCount; never logged.
|
|
235
|
+
return async (...args) => {
|
|
236
|
+
let callArgs = args;
|
|
237
|
+
if (args.length > 2) {
|
|
238
|
+
this.excessArityCount++;
|
|
239
|
+
callArgs = args.slice(0, 2);
|
|
240
|
+
}
|
|
251
241
|
try {
|
|
252
|
-
const result = await delegate(
|
|
242
|
+
const result = await delegate(...callArgs);
|
|
253
243
|
record();
|
|
254
244
|
return result;
|
|
255
245
|
}
|
|
@@ -258,6 +248,15 @@ export class HookLiveness {
|
|
|
258
248
|
}
|
|
259
249
|
};
|
|
260
250
|
}
|
|
251
|
+
/**
|
|
252
|
+
* v1.0.0 (K10-013 / plan §5.3) — records the `dispose` fire and
|
|
253
|
+
* flushes immediately, from inside the dispose delegate: this is the
|
|
254
|
+
* last write of the process, and nothing after it can persist.
|
|
255
|
+
*/
|
|
256
|
+
recordDispose() {
|
|
257
|
+
this.recordSuccess("dispose");
|
|
258
|
+
this.flush();
|
|
259
|
+
}
|
|
261
260
|
recordSuccess(hook) {
|
|
262
261
|
const c = this.counters.get(hook);
|
|
263
262
|
if (!c)
|
|
@@ -321,4 +320,3 @@ export function reduceVerdict(reports) {
|
|
|
321
320
|
reason: `${pending} hook(s) without checkpoint`,
|
|
322
321
|
};
|
|
323
322
|
}
|
|
324
|
-
//# sourceMappingURL=HookLiveness.js.map
|