@jmtrin/opencode-kevin 0.8.0 → 1.0.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 +559 -771
- package/dist/migrations/010_v09_native.sql +78 -0
- package/dist/migrations/011_v10_proven.sql +69 -0
- package/dist/plugin/Archiver.js +0 -1
- package/dist/plugin/ArtifactWriter.d.ts +25 -10
- package/dist/plugin/ArtifactWriter.js +43 -25
- package/dist/plugin/CausalChain.js +0 -1
- package/dist/plugin/ConflictDetector.js +0 -1
- 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 +0 -1
- package/dist/plugin/HookLiveness.d.ts +87 -0
- package/dist/plugin/HookLiveness.js +338 -0
- package/dist/plugin/InjectionLedger.js +0 -1
- package/dist/plugin/LessonFixer.js +0 -1
- package/dist/plugin/Materializer.d.ts +25 -0
- package/dist/plugin/Materializer.js +39 -11
- package/dist/plugin/MemoryService.js +0 -1
- package/dist/plugin/Migrate.d.ts +1 -0
- package/dist/plugin/Migrate.js +69 -6
- 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.d.ts +20 -9
- package/dist/plugin/RepoIdentity.js +44 -10
- package/dist/plugin/RepoTruth.js +0 -1
- package/dist/plugin/Retrospective.js +16 -1
- package/dist/plugin/SharedLayer.js +0 -1
- package/dist/plugin/Store.js +0 -1
- package/dist/plugin/ToolCallObserver.js +0 -1
- package/dist/plugin/capabilities.js +0 -1
- package/dist/plugin/confidence.js +0 -1
- package/dist/plugin/contract.d.ts +46 -0
- package/dist/plugin/contract.js +368 -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.d.ts +70 -0
- package/dist/plugin/host.js +250 -0
- package/dist/plugin/index.d.ts +2 -2
- package/dist/plugin/index.js +542 -222
- package/dist/plugin/inferability.js +0 -1
- package/dist/plugin/kevin_approve.js +0 -1
- package/dist/plugin/kevin_audit.d.ts +60 -0
- package/dist/plugin/kevin_audit.js +144 -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 +62 -0
- package/dist/plugin/kevin_doctor.js +196 -0
- package/dist/plugin/kevin_facts.js +0 -1
- package/dist/plugin/kevin_native.d.ts +29 -0
- package/dist/plugin/kevin_native.js +79 -0
- package/dist/plugin/kevin_propose.js +0 -1
- package/dist/plugin/kevin_publish.js +0 -1
- package/dist/plugin/kevin_why.js +0 -1
- package/dist/plugin/memory-format.js +0 -1
- package/dist/plugin/metrics.js +0 -1
- package/dist/plugin/native.d.ts +92 -0
- package/dist/plugin/native.js +190 -0
- 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 +0 -1
- 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/uuid.js +0 -1
- package/package.json +22 -7
- 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/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/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_facts.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/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
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
-- ============================================================
|
|
2
|
+
-- Kevin v0.9.0 "Native" — host surface and hook liveness
|
|
3
|
+
-- Migration 010. Additive only. No table rebuild.
|
|
4
|
+
-- ============================================================
|
|
5
|
+
|
|
6
|
+
-- 1. Hook liveness. Deliberately NOT project-scoped: a hook is a
|
|
7
|
+
-- property of the host binary, not of a checkout. One row per
|
|
8
|
+
-- hook name, updated in place. See D9-08.
|
|
9
|
+
CREATE TABLE IF NOT EXISTS hook_liveness (
|
|
10
|
+
hook TEXT PRIMARY KEY,
|
|
11
|
+
experimental INTEGER NOT NULL DEFAULT 0,
|
|
12
|
+
fire_count INTEGER NOT NULL DEFAULT 0,
|
|
13
|
+
error_count INTEGER NOT NULL DEFAULT 0,
|
|
14
|
+
expected_count INTEGER NOT NULL DEFAULT 0,
|
|
15
|
+
first_seen_at TEXT,
|
|
16
|
+
last_seen_at TEXT,
|
|
17
|
+
dead_since TEXT,
|
|
18
|
+
plugin_version TEXT
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
CREATE INDEX IF NOT EXISTS idx_hook_liveness_dead
|
|
22
|
+
ON hook_liveness(dead_since);
|
|
23
|
+
|
|
24
|
+
-- 2. Host probe history. Append-only. Off by default; exists so a
|
|
25
|
+
-- user chasing an intermittent fault can turn it on and get a
|
|
26
|
+
-- timeline instead of a single current value.
|
|
27
|
+
CREATE TABLE IF NOT EXISTS host_probes (
|
|
28
|
+
id TEXT PRIMARY KEY,
|
|
29
|
+
probed_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
30
|
+
plugin_version TEXT,
|
|
31
|
+
flavour TEXT NOT NULL,
|
|
32
|
+
has_shell INTEGER NOT NULL DEFAULT 0,
|
|
33
|
+
v2_skill INTEGER NOT NULL DEFAULT 0,
|
|
34
|
+
v2_reference INTEGER NOT NULL DEFAULT 0,
|
|
35
|
+
notes TEXT
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
CREATE INDEX IF NOT EXISTS idx_host_probes_at
|
|
39
|
+
ON host_probes(probed_at);
|
|
40
|
+
|
|
41
|
+
-- 3. Native registration outcomes. One row per attach attempt, so
|
|
42
|
+
-- "registered but unverified" is a queryable state rather than a
|
|
43
|
+
-- log line.
|
|
44
|
+
CREATE TABLE IF NOT EXISTS native_registrations (
|
|
45
|
+
id TEXT PRIMARY KEY,
|
|
46
|
+
attached_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
47
|
+
surface TEXT NOT NULL CHECK (surface IN ('skill', 'reference')),
|
|
48
|
+
registered INTEGER NOT NULL DEFAULT 0,
|
|
49
|
+
verified INTEGER NOT NULL DEFAULT 0,
|
|
50
|
+
note TEXT
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
CREATE INDEX IF NOT EXISTS idx_native_registrations_surface
|
|
54
|
+
ON native_registrations(surface, attached_at);
|
|
55
|
+
|
|
56
|
+
-- 4. Metric seeds (39 -> 45).
|
|
57
|
+
INSERT OR IGNORE INTO kevin_metrics (key, value) VALUES
|
|
58
|
+
('hook_fires_total', 0),
|
|
59
|
+
('hook_errors_total', 0),
|
|
60
|
+
('hooks_dead_total', 0),
|
|
61
|
+
('injections_suppressed_dead_hook', 0),
|
|
62
|
+
('native_registrations_total', 0),
|
|
63
|
+
('native_registration_failures', 0);
|
|
64
|
+
|
|
65
|
+
-- 5. Setting seeds (23 -> 27).
|
|
66
|
+
-- hook_liveness_enabled defaults ON: it is a read-only instrument
|
|
67
|
+
-- on the success path, and an instrument nobody switches on is an
|
|
68
|
+
-- instrument nobody has.
|
|
69
|
+
-- native_registration_enabled defaults OFF: it changes where a
|
|
70
|
+
-- curated skill comes from, and that is a change a user opts into.
|
|
71
|
+
INSERT OR IGNORE INTO kevin_settings (key, value) VALUES
|
|
72
|
+
('hook_liveness_enabled', '1'),
|
|
73
|
+
('native_registration_enabled', '0'),
|
|
74
|
+
('host_probe_history_enabled', '0'),
|
|
75
|
+
('dead_hook_report_threshold', '3');
|
|
76
|
+
|
|
77
|
+
-- 6. Version marker.
|
|
78
|
+
INSERT OR IGNORE INTO schema_version (version) VALUES ('010');
|
|
@@ -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');
|
package/dist/plugin/Archiver.js
CHANGED
|
@@ -96,4 +96,3 @@ function formatSqliteUtc(d) {
|
|
|
96
96
|
const pad = (n) => String(n).padStart(2, "0");
|
|
97
97
|
return `${d.getUTCFullYear()}-${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())} ${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}:${pad(d.getUTCSeconds())}`;
|
|
98
98
|
}
|
|
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
|
package/dist/plugin/Curator.js
CHANGED
package/dist/plugin/Feedback.js
CHANGED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { HOOK_NAMES } from "./Migrate.js";
|
|
2
|
+
import type { Store } from "./Store.js";
|
|
3
|
+
export type HookName = (typeof HOOK_NAMES)[number];
|
|
4
|
+
export type LivenessState = "live" | "dead" | "unknown";
|
|
5
|
+
export interface HookReport {
|
|
6
|
+
hook: HookName;
|
|
7
|
+
experimental: boolean;
|
|
8
|
+
state: LivenessState;
|
|
9
|
+
firstSeenAt: string | null;
|
|
10
|
+
lastSeenAt: string | null;
|
|
11
|
+
fireCount: number;
|
|
12
|
+
expectedCount: number;
|
|
13
|
+
/** v0.9.0 (K9-012) — set once when the hook went dead; never cleared. */
|
|
14
|
+
deadSince: string | null;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* v0.9.0 (K9-010 / plan §5.3, D9-09) — parse the TEXT setting
|
|
18
|
+
* `dead_hook_report_threshold` into the [1, 1000] clamp. NaN and empty
|
|
19
|
+
* fall back to 3 (never 0: a zero threshold would report every hook dead
|
|
20
|
+
* before a single session completes a checkpoint).
|
|
21
|
+
*/
|
|
22
|
+
export declare function parseThreshold(text: string | null | undefined): number;
|
|
23
|
+
interface HookLivenessOptions {
|
|
24
|
+
/** `hook_liveness_enabled === "1"` (TEXT comparison, decided at construction). */
|
|
25
|
+
enabled: boolean;
|
|
26
|
+
/** Raw TEXT of `dead_hook_report_threshold`; parsed+clamped in report(). */
|
|
27
|
+
thresholdText: string | null | undefined;
|
|
28
|
+
/** `host.pluginVersion` at construction, stored on every flushed row. */
|
|
29
|
+
pluginVersion: string | null;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* v0.9.0 (K9-009/K9-010 / plan §5.3) — wraps the plugin's hooks with a
|
|
33
|
+
* success-path recorder, dedups per-session checkpoints, and reports each
|
|
34
|
+
* hook's liveness state. Persistence is machine-scoped: the hook_liveness
|
|
35
|
+
* table carries no project_id or repo_id (D9-08).
|
|
36
|
+
*/
|
|
37
|
+
export declare class HookLiveness {
|
|
38
|
+
private readonly store;
|
|
39
|
+
private readonly options;
|
|
40
|
+
private readonly counters;
|
|
41
|
+
private readonly seenSessions;
|
|
42
|
+
private readonly suppressedSessions;
|
|
43
|
+
private readonly threshold;
|
|
44
|
+
private readonly pluginVersion;
|
|
45
|
+
constructor(store: Store, options: HookLivenessOptions);
|
|
46
|
+
/**
|
|
47
|
+
* Returns a new object with the same keys; function values are replaced
|
|
48
|
+
* by delegating wrappers, non-function values (the tool map) pass by
|
|
49
|
+
* reference. When liveness is disabled, returns the argument unchanged —
|
|
50
|
+
* the same object, not a copy.
|
|
51
|
+
*/
|
|
52
|
+
wrap<T>(hooks: T): T;
|
|
53
|
+
/**
|
|
54
|
+
* v0.9.0 (K9-010 / plan §5.3) — a checkpoint: the session reached a
|
|
55
|
+
* model turn (tool.execute.after fired), so the system prompt was
|
|
56
|
+
* assembled and `experimental.chat.system.transform` MUST have been
|
|
57
|
+
* offered. Deduped per session: 20 tool calls in one session count
|
|
58
|
+
* exactly one expectation.
|
|
59
|
+
*/
|
|
60
|
+
expect(hook: HookName, sessionID: string): void;
|
|
61
|
+
/**
|
|
62
|
+
* Per-hook liveness verdicts in canonical HOOK_NAMES order.
|
|
63
|
+
* A hook that fired once is live forever; dead requires both zero fires
|
|
64
|
+
* and `expectedCount >= threshold`; everything else is unknown — and
|
|
65
|
+
* unknown is never rounded to healthy (D9-09).
|
|
66
|
+
*/
|
|
67
|
+
report(): HookReport[];
|
|
68
|
+
/** Persists every counter row. Called on the metrics.flush() cadence. */
|
|
69
|
+
flush(): void;
|
|
70
|
+
private makeWrapper;
|
|
71
|
+
/**
|
|
72
|
+
* v1.0.0 (K10-013 / plan §5.3) — records the `dispose` fire and
|
|
73
|
+
* flushes immediately, from inside the dispose delegate: this is the
|
|
74
|
+
* last write of the process, and nothing after it can persist.
|
|
75
|
+
*/
|
|
76
|
+
recordDispose(): void;
|
|
77
|
+
private recordSuccess;
|
|
78
|
+
private recordError;
|
|
79
|
+
private loadFromDb;
|
|
80
|
+
}
|
|
81
|
+
export type HostVerdict = "healthy" | "degraded" | "unknown";
|
|
82
|
+
export interface HostVerdictResult {
|
|
83
|
+
verdict: HostVerdict;
|
|
84
|
+
reason: string;
|
|
85
|
+
}
|
|
86
|
+
export declare function reduceVerdict(reports: readonly HookReport[]): HostVerdictResult;
|
|
87
|
+
export {};
|