@jmtrin/opencode-kevin 1.2.0 → 1.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/dist/plugin/index.d.ts +4 -3
- package/dist/plugin/index.js +122 -113
- package/dist/plugin/native.d.ts +2 -2
- package/dist/plugin/native.js +2 -2
- package/package.json +56 -68
- package/LICENSE +0 -21
- package/README.md +0 -605
- package/dist/migrations/001_initial.sql +0 -92
- package/dist/migrations/002_indexes.sql +0 -14
- package/dist/migrations/003_v02_signal.sql +0 -58
- package/dist/migrations/004_v03_knowledge.sql +0 -138
- package/dist/migrations/005_v04_signal.sql +0 -57
- package/dist/migrations/006_v05_glassbox.sql +0 -118
- package/dist/migrations/007_v06_pull.sql +0 -145
- package/dist/migrations/008_v07_truth.sql +0 -124
- package/dist/migrations/009_v08_team.sql +0 -100
- package/dist/migrations/010_v09_native.sql +0 -78
- package/dist/migrations/011_v10_proven.sql +0 -69
- package/dist/migrations/012_v11_drift.sql +0 -24
- package/dist/plugin/Archiver.d.ts +0 -42
- package/dist/plugin/Archiver.js +0 -83
- package/dist/plugin/ArtifactWriter.d.ts +0 -93
- package/dist/plugin/ArtifactWriter.js +0 -291
- package/dist/plugin/CausalChain.d.ts +0 -22
- package/dist/plugin/CausalChain.js +0 -197
- package/dist/plugin/ChatBridge.d.ts +0 -41
- package/dist/plugin/ChatBridge.js +0 -103
- package/dist/plugin/ConflictDetector.d.ts +0 -35
- package/dist/plugin/ConflictDetector.js +0 -260
- package/dist/plugin/ContextInjector.d.ts +0 -222
- package/dist/plugin/ContextInjector.js +0 -533
- package/dist/plugin/ConventionMiner.d.ts +0 -35
- package/dist/plugin/ConventionMiner.js +0 -242
- package/dist/plugin/Curator.d.ts +0 -116
- package/dist/plugin/Curator.js +0 -324
- package/dist/plugin/DashboardHtml.d.ts +0 -5
- package/dist/plugin/DashboardHtml.js +0 -180
- package/dist/plugin/Feedback.d.ts +0 -67
- package/dist/plugin/Feedback.js +0 -120
- package/dist/plugin/HookLiveness.d.ts +0 -88
- package/dist/plugin/HookLiveness.js +0 -322
- package/dist/plugin/InjectionLedger.d.ts +0 -98
- package/dist/plugin/InjectionLedger.js +0 -295
- package/dist/plugin/LessonFixer.d.ts +0 -44
- package/dist/plugin/LessonFixer.js +0 -45
- package/dist/plugin/Materializer.d.ts +0 -83
- package/dist/plugin/Materializer.js +0 -181
- package/dist/plugin/MemoryService.d.ts +0 -360
- package/dist/plugin/MemoryService.js +0 -1242
- package/dist/plugin/Migrate.d.ts +0 -17
- package/dist/plugin/Migrate.js +0 -271
- package/dist/plugin/PatternMiner.d.ts +0 -49
- package/dist/plugin/PatternMiner.js +0 -132
- package/dist/plugin/QualityGate.d.ts +0 -124
- package/dist/plugin/QualityGate.js +0 -119
- package/dist/plugin/Reflector.d.ts +0 -91
- package/dist/plugin/Reflector.js +0 -307
- package/dist/plugin/RepoIdentity.d.ts +0 -124
- package/dist/plugin/RepoIdentity.js +0 -300
- package/dist/plugin/RepoTruth.d.ts +0 -80
- package/dist/plugin/RepoTruth.js +0 -599
- package/dist/plugin/Retrospective.d.ts +0 -17
- package/dist/plugin/Retrospective.js +0 -216
- package/dist/plugin/SharedLayer.d.ts +0 -159
- package/dist/plugin/SharedLayer.js +0 -462
- package/dist/plugin/Store.d.ts +0 -29
- package/dist/plugin/Store.js +0 -50
- package/dist/plugin/ToolCallObserver.d.ts +0 -32
- package/dist/plugin/ToolCallObserver.js +0 -201
- package/dist/plugin/TuiActions.d.ts +0 -43
- package/dist/plugin/TuiActions.js +0 -181
- package/dist/plugin/TuiSnapshots.d.ts +0 -24
- package/dist/plugin/TuiSnapshots.js +0 -158
- package/dist/plugin/columns.d.ts +0 -11
- package/dist/plugin/columns.js +0 -54
- package/dist/plugin/confidence.d.ts +0 -8
- package/dist/plugin/confidence.js +0 -34
- package/dist/plugin/contract.d.ts +0 -54
- package/dist/plugin/contract.js +0 -386
- package/dist/plugin/diff.d.ts +0 -8
- package/dist/plugin/diff.js +0 -182
- package/dist/plugin/escape.d.ts +0 -39
- package/dist/plugin/escape.js +0 -60
- package/dist/plugin/fingerprint.d.ts +0 -27
- package/dist/plugin/fingerprint.js +0 -73
- package/dist/plugin/inferability.d.ts +0 -32
- package/dist/plugin/inferability.js +0 -88
- package/dist/plugin/kevin_approve.d.ts +0 -34
- package/dist/plugin/kevin_approve.js +0 -52
- package/dist/plugin/kevin_audit.d.ts +0 -229
- package/dist/plugin/kevin_audit.js +0 -631
- package/dist/plugin/kevin_bench.d.ts +0 -10
- package/dist/plugin/kevin_bench.js +0 -82
- package/dist/plugin/kevin_conflicts.d.ts +0 -9
- package/dist/plugin/kevin_conflicts.js +0 -50
- package/dist/plugin/kevin_contract.d.ts +0 -8
- package/dist/plugin/kevin_contract.js +0 -59
- package/dist/plugin/kevin_doctor.d.ts +0 -62
- package/dist/plugin/kevin_doctor.js +0 -196
- package/dist/plugin/kevin_facts.d.ts +0 -42
- package/dist/plugin/kevin_facts.js +0 -36
- package/dist/plugin/kevin_forget.d.ts +0 -33
- package/dist/plugin/kevin_forget.js +0 -260
- package/dist/plugin/kevin_native.d.ts +0 -29
- package/dist/plugin/kevin_native.js +0 -79
- package/dist/plugin/kevin_propose.d.ts +0 -23
- package/dist/plugin/kevin_propose.js +0 -14
- package/dist/plugin/kevin_publish.d.ts +0 -38
- package/dist/plugin/kevin_publish.js +0 -18
- package/dist/plugin/kevin_why.d.ts +0 -23
- package/dist/plugin/kevin_why.js +0 -111
- package/dist/plugin/memory-format.d.ts +0 -20
- package/dist/plugin/memory-format.js +0 -59
- package/dist/plugin/metrics.d.ts +0 -97
- package/dist/plugin/metrics.js +0 -288
- package/dist/plugin/okf-export.d.ts +0 -3
- package/dist/plugin/okf-export.js +0 -134
- package/dist/plugin/okf-import.d.ts +0 -76
- package/dist/plugin/okf-import.js +0 -271
- package/dist/plugin/okf.d.ts +0 -107
- package/dist/plugin/okf.js +0 -303
- package/dist/plugin/perf.d.ts +0 -35
- package/dist/plugin/perf.js +0 -174
- package/dist/plugin/query-tokenizer.d.ts +0 -13
- package/dist/plugin/query-tokenizer.js +0 -133
- package/dist/plugin/redact.d.ts +0 -2
- package/dist/plugin/redact.js +0 -18
- package/dist/plugin/replay-types.d.ts +0 -69
- package/dist/plugin/replay-types.js +0 -156
- package/dist/plugin/replay.d.ts +0 -36
- package/dist/plugin/replay.js +0 -202
- package/dist/plugin/sqlite-adapter.d.ts +0 -12
- package/dist/plugin/sqlite-adapter.js +0 -81
- package/dist/plugin/time-ms.d.ts +0 -1
- package/dist/plugin/time-ms.js +0 -16
- package/dist/plugin/tui-types.d.ts +0 -59
- package/dist/plugin/tui-types.js +0 -4
- package/dist/plugin/tui.d.ts +0 -18
- package/dist/plugin/tui.js +0 -198
- package/dist/plugin/uuid.d.ts +0 -1
- package/dist/plugin/uuid.js +0 -50
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* K10-019 — kevin_bench: reports what `npm run bench` recorded.
|
|
3
|
-
*
|
|
4
|
-
* It NEVER runs the benchmark — running it from inside a live session
|
|
5
|
-
* would measure the session and pollute the user's database. The module
|
|
6
|
-
* imports nothing from scripts/bench.ts; the corpus digest of the on-disk
|
|
7
|
-
* corpus is recomputed here with the same fnv1a64 discipline so a result
|
|
8
|
-
* computed from a different corpus is visibly a different measurement.
|
|
9
|
-
*/
|
|
10
|
-
import { readFileSync, statSync } from "node:fs";
|
|
11
|
-
import { join } from "node:path";
|
|
12
|
-
import { fnv1a64 } from "./fingerprint.js";
|
|
13
|
-
function onDiskCorpusDigest(cwd) {
|
|
14
|
-
try {
|
|
15
|
-
const dir = join(cwd, "bench", "corpus");
|
|
16
|
-
statSync(dir);
|
|
17
|
-
const mem = readFileSync(join(dir, "memories.jsonl"), "utf8");
|
|
18
|
-
const q = readFileSync(join(dir, "queries.jsonl"), "utf8");
|
|
19
|
-
return {
|
|
20
|
-
digest: fnv1a64(`${mem}\n${q}`),
|
|
21
|
-
queries: q.split("\n").filter((l) => l.trim().length > 0).length,
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
catch {
|
|
25
|
-
return null;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
function noRuns() {
|
|
29
|
-
return {
|
|
30
|
-
has_runs: false,
|
|
31
|
-
hint: "npm run bench",
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
export function buildKevinBench(deps, args) {
|
|
35
|
-
if (args.action === "status") {
|
|
36
|
-
const count = deps.store
|
|
37
|
-
.prepare("SELECT COUNT(*) AS c FROM bench_runs")
|
|
38
|
-
.get();
|
|
39
|
-
if (!count || count.c === 0)
|
|
40
|
-
return noRuns();
|
|
41
|
-
const latest = deps.store
|
|
42
|
-
.prepare("SELECT corpus_digest, contract_digest, package_version, runtime, ran_at FROM bench_runs ORDER BY id DESC LIMIT 1")
|
|
43
|
-
.get();
|
|
44
|
-
if (!latest)
|
|
45
|
-
return noRuns();
|
|
46
|
-
const disk = onDiskCorpusDigest(deps.cwd ?? process.cwd());
|
|
47
|
-
return {
|
|
48
|
-
has_runs: true,
|
|
49
|
-
total_runs: count.c,
|
|
50
|
-
corpus_digest: latest.corpus_digest,
|
|
51
|
-
contract_digest: latest.contract_digest,
|
|
52
|
-
package_version: latest.package_version,
|
|
53
|
-
runtime: latest.runtime,
|
|
54
|
-
last_ran_at: latest.ran_at,
|
|
55
|
-
corpus_on_disk: disk === null ? null : disk.digest,
|
|
56
|
-
matches_disk_corpus: disk !== null && disk.digest === latest.corpus_digest,
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
const latest = deps.store
|
|
60
|
-
.prepare("SELECT corpus_digest, contract_digest, package_version, runtime, ran_at FROM bench_runs ORDER BY id DESC LIMIT 1")
|
|
61
|
-
.get();
|
|
62
|
-
if (!latest)
|
|
63
|
-
return noRuns();
|
|
64
|
-
const rows = deps.store
|
|
65
|
-
.prepare("SELECT arm, k, precision_at_k, recall_at_k, mrr FROM bench_runs WHERE corpus_digest = ? AND ran_at = ?")
|
|
66
|
-
.all(latest.corpus_digest, latest.ran_at);
|
|
67
|
-
return {
|
|
68
|
-
has_runs: true,
|
|
69
|
-
corpus_digest: latest.corpus_digest,
|
|
70
|
-
contract_digest: latest.contract_digest,
|
|
71
|
-
package_version: latest.package_version,
|
|
72
|
-
runtime: latest.runtime,
|
|
73
|
-
ran_at: latest.ran_at,
|
|
74
|
-
arms: rows.map((r) => ({
|
|
75
|
-
arm: r.arm,
|
|
76
|
-
k: r.k,
|
|
77
|
-
precision_at_k: r.precision_at_k,
|
|
78
|
-
recall_at_k: r.recall_at_k,
|
|
79
|
-
mrr: r.mrr,
|
|
80
|
-
})),
|
|
81
|
-
};
|
|
82
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import type { ConflictDetector } from "./ConflictDetector.js";
|
|
2
|
-
import type { Store } from "./Store.js";
|
|
3
|
-
export interface KevinConflictsDeps {
|
|
4
|
-
store: Store;
|
|
5
|
-
detector: ConflictDetector;
|
|
6
|
-
projectId: string;
|
|
7
|
-
}
|
|
8
|
-
export type ConflictAction = "list" | "acknowledge" | "resolve";
|
|
9
|
-
export declare function executeKevinConflicts(deps: KevinConflictsDeps, action: ConflictAction, id?: string, keep?: string, requestedStatus?: "open" | "acknowledged" | "resolved"): Record<string, unknown>;
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
export function executeKevinConflicts(deps, action, id, keep, requestedStatus) {
|
|
2
|
-
if (action === "acknowledge") {
|
|
3
|
-
if (!id)
|
|
4
|
-
return { error: "missing_id" };
|
|
5
|
-
// Mirror resolve()'s honesty: acknowledging a nonexistent or foreign id
|
|
6
|
-
// must not be reported as a success.
|
|
7
|
-
const row = deps.store
|
|
8
|
-
.prepare("SELECT id FROM memory_conflicts WHERE id = ? AND project_id = ?")
|
|
9
|
-
.get(id, deps.projectId);
|
|
10
|
-
if (!row)
|
|
11
|
-
return { error: "not_found", id };
|
|
12
|
-
deps.detector.acknowledge(id);
|
|
13
|
-
return { id, status: "acknowledged" };
|
|
14
|
-
}
|
|
15
|
-
if (action === "resolve") {
|
|
16
|
-
if (!id || !keep)
|
|
17
|
-
return { error: "missing_id_or_keep" };
|
|
18
|
-
const row = deps.store
|
|
19
|
-
.prepare("SELECT memory_a, memory_b FROM memory_conflicts WHERE id = ? AND project_id = ?")
|
|
20
|
-
.get(id, deps.projectId);
|
|
21
|
-
if (!row)
|
|
22
|
-
return { error: "not_found", id };
|
|
23
|
-
if (keep !== row.memory_a && keep !== row.memory_b) {
|
|
24
|
-
return { error: "invalid_keep", id, keep };
|
|
25
|
-
}
|
|
26
|
-
deps.detector.resolve(id, keep);
|
|
27
|
-
return {
|
|
28
|
-
id,
|
|
29
|
-
status: "resolved",
|
|
30
|
-
kept: keep,
|
|
31
|
-
other: keep === row.memory_a ? row.memory_b : row.memory_a,
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
const status = requestedStatus ?? "open";
|
|
35
|
-
const rows = deps.store
|
|
36
|
-
.prepare(`SELECT id, kind, memory_a, memory_b, fact_id, detail, status, detected_at
|
|
37
|
-
FROM memory_conflicts WHERE project_id = ? AND status = ?
|
|
38
|
-
ORDER BY detected_at, id`)
|
|
39
|
-
.all(deps.projectId, status);
|
|
40
|
-
const counts = deps.store
|
|
41
|
-
.prepare(`SELECT status, COUNT(*) AS n FROM memory_conflicts
|
|
42
|
-
WHERE project_id = ? GROUP BY status`)
|
|
43
|
-
.all(deps.projectId);
|
|
44
|
-
return {
|
|
45
|
-
open: counts.find((row) => row.status === "open")?.n ?? 0,
|
|
46
|
-
acknowledged: counts.find((row) => row.status === "acknowledged")?.n ?? 0,
|
|
47
|
-
resolved: counts.find((row) => row.status === "resolved")?.n ?? 0,
|
|
48
|
-
conflicts: rows,
|
|
49
|
-
};
|
|
50
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
export interface KevinContractDeps {
|
|
2
|
-
packageVersion: string;
|
|
3
|
-
}
|
|
4
|
-
export interface KevinContractArgs {
|
|
5
|
-
clause?: string;
|
|
6
|
-
format?: "summary" | "full";
|
|
7
|
-
}
|
|
8
|
-
export declare function buildKevinContract(deps: KevinContractDeps, args: KevinContractArgs): Record<string, unknown>;
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* K10-018 — kevin_contract: the frozen surface, inspectable at runtime.
|
|
3
|
-
*
|
|
4
|
-
* Read-only. Returns the live contract with its digest and per-clause
|
|
5
|
-
* stability/since/deprecation; with `clause`, one clause's full value.
|
|
6
|
-
* No filesystem paths, no project ids, no LLM, no network.
|
|
7
|
-
*/
|
|
8
|
-
import { contractDigest, describeContract, } from "./contract.js";
|
|
9
|
-
export function buildKevinContract(deps, args) {
|
|
10
|
-
const contract = describeContract();
|
|
11
|
-
const digest = contractDigest(contract);
|
|
12
|
-
const format = args.format ?? "summary";
|
|
13
|
-
if (args.clause !== undefined) {
|
|
14
|
-
const clause = contract.clauses.find((c) => c.id === args.clause);
|
|
15
|
-
if (!clause) {
|
|
16
|
-
return {
|
|
17
|
-
error: "unknown_clause",
|
|
18
|
-
clause: args.clause,
|
|
19
|
-
known_clauses: contract.clauses.map((c) => c.id),
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
return {
|
|
23
|
-
contract_version: contract.contractVersion,
|
|
24
|
-
digest,
|
|
25
|
-
package_version: deps.packageVersion,
|
|
26
|
-
clause: {
|
|
27
|
-
id: clause.id,
|
|
28
|
-
title: clause.title,
|
|
29
|
-
stability: clause.stability,
|
|
30
|
-
since: clause.since,
|
|
31
|
-
...(clause.deprecated ? { deprecated: clause.deprecated } : {}),
|
|
32
|
-
...(clause.replacement ? { replacement: clause.replacement } : {}),
|
|
33
|
-
value: clause.value,
|
|
34
|
-
},
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
if (format === "full") {
|
|
38
|
-
return {
|
|
39
|
-
contract_version: contract.contractVersion,
|
|
40
|
-
digest,
|
|
41
|
-
package_version: deps.packageVersion,
|
|
42
|
-
clauses: contract.clauses,
|
|
43
|
-
deprecated: contract.clauses.filter((c) => c.deprecated).map((c) => c.id),
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
return {
|
|
47
|
-
contract_version: contract.contractVersion,
|
|
48
|
-
digest,
|
|
49
|
-
package_version: deps.packageVersion,
|
|
50
|
-
clauses: contract.clauses.map((c) => ({
|
|
51
|
-
id: c.id,
|
|
52
|
-
title: c.title,
|
|
53
|
-
stability: c.stability,
|
|
54
|
-
since: c.since,
|
|
55
|
-
...(c.deprecated ? { deprecated: c.deprecated } : {}),
|
|
56
|
-
})),
|
|
57
|
-
deprecated: contract.clauses.filter((c) => c.deprecated).map((c) => c.id),
|
|
58
|
-
};
|
|
59
|
-
}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import type { Store } from "./Store.js";
|
|
2
|
-
import type { HostSurface } from "./host.js";
|
|
3
|
-
import type { SettingsReader } from "./native.js";
|
|
4
|
-
export interface DoctorHook {
|
|
5
|
-
readonly hook: string;
|
|
6
|
-
readonly experimental: boolean;
|
|
7
|
-
readonly state: "live" | "dead" | "unknown";
|
|
8
|
-
readonly fire_count: number;
|
|
9
|
-
readonly expected_count: number;
|
|
10
|
-
readonly since?: string;
|
|
11
|
-
}
|
|
12
|
-
export interface DoctorReport {
|
|
13
|
-
readonly host: {
|
|
14
|
-
readonly plugin_version: string | null;
|
|
15
|
-
readonly flavour: string;
|
|
16
|
-
readonly shell_available: boolean;
|
|
17
|
-
readonly v2: {
|
|
18
|
-
readonly skill: boolean;
|
|
19
|
-
readonly reference: boolean;
|
|
20
|
-
};
|
|
21
|
-
};
|
|
22
|
-
readonly hooks: DoctorHook[];
|
|
23
|
-
readonly dependencies: {
|
|
24
|
-
readonly declared: string[];
|
|
25
|
-
readonly zod_copies: number | null;
|
|
26
|
-
readonly note?: string;
|
|
27
|
-
};
|
|
28
|
-
readonly native: {
|
|
29
|
-
readonly enabled: boolean;
|
|
30
|
-
readonly registered: {
|
|
31
|
-
readonly skill: boolean;
|
|
32
|
-
readonly reference: boolean;
|
|
33
|
-
};
|
|
34
|
-
readonly verified: {
|
|
35
|
-
readonly skill: boolean;
|
|
36
|
-
readonly reference: boolean;
|
|
37
|
-
};
|
|
38
|
-
};
|
|
39
|
-
/** v1.0.0 (K10-021) — scopes whose most recent sample exceeded their
|
|
40
|
-
* p95 budget. Omitted entirely on pre-011 databases (no table). */
|
|
41
|
-
readonly perf?: {
|
|
42
|
-
readonly scopes_over_budget: readonly string[];
|
|
43
|
-
};
|
|
44
|
-
readonly verdict: "healthy" | "degraded" | "unknown";
|
|
45
|
-
readonly reason: string;
|
|
46
|
-
readonly partial: boolean;
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* Count resolved zod copies under `cwd`'s dependency tree. The expected
|
|
50
|
-
* value after K9-005 is 1; a second copy is exactly the regression this
|
|
51
|
-
* field exists to catch. On any walk failure the count is `null` and the
|
|
52
|
-
* note says so — never a guess.
|
|
53
|
-
*/
|
|
54
|
-
export declare function countZodCopies(cwd: string): {
|
|
55
|
-
copies: number | null;
|
|
56
|
-
note?: string;
|
|
57
|
-
};
|
|
58
|
-
export interface DoctorOptions {
|
|
59
|
-
/** Directory to walk for zod copies; defaults to process.cwd(). */
|
|
60
|
-
readonly zodRoot?: string;
|
|
61
|
-
}
|
|
62
|
-
export declare function buildDoctor(store: Store, host: HostSurface, settings: SettingsReader, options?: DoctorOptions): DoctorReport;
|
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
import { existsSync, readdirSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { reduceVerdict, } from "./HookLiveness.js";
|
|
4
|
-
import { HOOK_NAMES } from "./Migrate.js";
|
|
5
|
-
/** Dead first, then unknown, then live — the failure is the first thing
|
|
6
|
-
* on screen. Ties break on the canonical hook name. */
|
|
7
|
-
const STATE_ORDER = {
|
|
8
|
-
dead: 0,
|
|
9
|
-
unknown: 1,
|
|
10
|
-
live: 2,
|
|
11
|
-
};
|
|
12
|
-
/** The hooks block is pure SQL over the persisted hook_liveness table:
|
|
13
|
-
* the dead flag is materialized at expect() time (K9-010) so flush() can
|
|
14
|
-
* persist dead_since even when report() never runs — this block reads
|
|
15
|
-
* that persisted state, independent of any live HookLiveness object. */
|
|
16
|
-
function hooksBlock(store) {
|
|
17
|
-
let rows = [];
|
|
18
|
-
try {
|
|
19
|
-
rows = store
|
|
20
|
-
.prepare("SELECT hook, experimental, fire_count, expected_count, dead_since FROM hook_liveness")
|
|
21
|
-
.all();
|
|
22
|
-
}
|
|
23
|
-
catch {
|
|
24
|
-
return { hooks: [], partial: true };
|
|
25
|
-
}
|
|
26
|
-
// Only hooks the plugin knows about are reported; a foreign row (from
|
|
27
|
-
// a future release) is skipped, mirroring HookLiveness.loadFromDb.
|
|
28
|
-
const reports = rows
|
|
29
|
-
.filter((row) => HOOK_NAMES.includes(row.hook))
|
|
30
|
-
.map((row) => ({
|
|
31
|
-
hook: row.hook,
|
|
32
|
-
experimental: row.experimental === 1,
|
|
33
|
-
state: row.fire_count > 0
|
|
34
|
-
? "live"
|
|
35
|
-
: row.dead_since !== null
|
|
36
|
-
? "dead"
|
|
37
|
-
: "unknown",
|
|
38
|
-
firstSeenAt: null,
|
|
39
|
-
lastSeenAt: null,
|
|
40
|
-
fireCount: row.fire_count,
|
|
41
|
-
expectedCount: row.expected_count,
|
|
42
|
-
deadSince: row.dead_since,
|
|
43
|
-
}));
|
|
44
|
-
reports.sort((a, b) => STATE_ORDER[a.state] - STATE_ORDER[b.state] ||
|
|
45
|
-
a.hook.localeCompare(b.hook));
|
|
46
|
-
const hooks = reports.map((r) => ({
|
|
47
|
-
hook: r.hook,
|
|
48
|
-
experimental: r.experimental,
|
|
49
|
-
state: r.state,
|
|
50
|
-
fire_count: r.fireCount,
|
|
51
|
-
expected_count: r.expectedCount,
|
|
52
|
-
...(r.deadSince !== null ? { since: r.deadSince } : {}),
|
|
53
|
-
}));
|
|
54
|
-
return { hooks, partial: false };
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Count resolved zod copies under `cwd`'s dependency tree. The expected
|
|
58
|
-
* value after K9-005 is 1; a second copy is exactly the regression this
|
|
59
|
-
* field exists to catch. On any walk failure the count is `null` and the
|
|
60
|
-
* note says so — never a guess.
|
|
61
|
-
*/
|
|
62
|
-
export function countZodCopies(cwd) {
|
|
63
|
-
try {
|
|
64
|
-
if (!existsSync(cwd)) {
|
|
65
|
-
return { copies: null, note: "zod resolution walk failed" };
|
|
66
|
-
}
|
|
67
|
-
const seen = new Set();
|
|
68
|
-
const stack = [cwd];
|
|
69
|
-
let copies = 0;
|
|
70
|
-
// Bounded walk: node_modules is normally shallow; nested copies
|
|
71
|
-
// (node_modules/<pkg>/node_modules/zod) sit within a few levels.
|
|
72
|
-
for (let depth = 0; stack.length > 0 && depth < 8; depth += 1) {
|
|
73
|
-
const next = [];
|
|
74
|
-
for (const dir of stack) {
|
|
75
|
-
const nm = join(dir, "node_modules");
|
|
76
|
-
if (!existsSync(nm))
|
|
77
|
-
continue;
|
|
78
|
-
let entries;
|
|
79
|
-
try {
|
|
80
|
-
entries = readdirSync(nm, { withFileTypes: true });
|
|
81
|
-
}
|
|
82
|
-
catch {
|
|
83
|
-
continue;
|
|
84
|
-
}
|
|
85
|
-
for (const entry of entries) {
|
|
86
|
-
if (!entry.isDirectory())
|
|
87
|
-
continue;
|
|
88
|
-
if (entry.name === "zod") {
|
|
89
|
-
const pkg = join(nm, "zod", "package.json");
|
|
90
|
-
if (existsSync(pkg) && !seen.has(pkg)) {
|
|
91
|
-
seen.add(pkg);
|
|
92
|
-
copies += 1;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
else if (entry.name !== ".bin" && entry.name !== ".cache") {
|
|
96
|
-
next.push(join(nm, entry.name));
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
stack.length = 0;
|
|
101
|
-
stack.push(...next);
|
|
102
|
-
}
|
|
103
|
-
return { copies };
|
|
104
|
-
}
|
|
105
|
-
catch {
|
|
106
|
-
return { copies: null, note: "zod resolution walk failed" };
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
/** v1.0.0 (K10-021) — scopes whose most recent perf sample breached the
|
|
110
|
-
* p95 budget. The within_budget flag is materialized by Perf.flush()
|
|
111
|
-
* against the frozen BUDGETS; this block reads that persisted verdict
|
|
112
|
-
* and never re-aggregates. A missing table (pre-011 DB) yields no
|
|
113
|
-
* block rather than an empty one, matching the hooks precedent. */
|
|
114
|
-
function perfOverBudget(store) {
|
|
115
|
-
try {
|
|
116
|
-
const rows = store
|
|
117
|
-
.prepare("SELECT DISTINCT scope FROM perf_samples WHERE within_budget = 0 ORDER BY scope")
|
|
118
|
-
.all();
|
|
119
|
-
return rows.map((r) => r.scope);
|
|
120
|
-
}
|
|
121
|
-
catch {
|
|
122
|
-
return null;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
function lastRegistration(store, surface) {
|
|
126
|
-
try {
|
|
127
|
-
const row = store
|
|
128
|
-
.prepare("SELECT surface, registered, verified FROM native_registrations WHERE surface = ? ORDER BY attached_at DESC, id DESC LIMIT 1")
|
|
129
|
-
.get(surface);
|
|
130
|
-
if (!row)
|
|
131
|
-
return { registered: false, verified: false };
|
|
132
|
-
return { registered: row.registered === 1, verified: row.verified === 1 };
|
|
133
|
-
}
|
|
134
|
-
catch {
|
|
135
|
-
return { registered: false, verified: false };
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
export function buildDoctor(store, host, settings, options = {}) {
|
|
139
|
-
// The host block comes from the frozen probe result captured at
|
|
140
|
-
// construction — kevin_doctor never re-probes (K9-004: probe once,
|
|
141
|
-
// freeze, reuse).
|
|
142
|
-
const hooks = hooksBlock(store);
|
|
143
|
-
let verdict = reduceVerdict(hooks.hooks.map((h) => ({
|
|
144
|
-
hook: h.hook,
|
|
145
|
-
experimental: h.experimental,
|
|
146
|
-
state: h.state,
|
|
147
|
-
firstSeenAt: null,
|
|
148
|
-
lastSeenAt: null,
|
|
149
|
-
fireCount: h.fire_count,
|
|
150
|
-
expectedCount: h.expected_count,
|
|
151
|
-
deadSince: h.since ?? null,
|
|
152
|
-
})));
|
|
153
|
-
// v1.0.0 (K10-021) — a slow plugin is not healthy even when every
|
|
154
|
-
// hook is live. A budget breach degrades the verdict and names the
|
|
155
|
-
// scope; a dead hook keeps its reason; `unknown` is never rounded up.
|
|
156
|
-
const breaches = perfOverBudget(store);
|
|
157
|
-
if (breaches !== null &&
|
|
158
|
-
breaches.length > 0 &&
|
|
159
|
-
verdict.verdict !== "degraded") {
|
|
160
|
-
verdict = {
|
|
161
|
-
verdict: "degraded",
|
|
162
|
-
reason: `perf budget exceeded: ${breaches.join(", ")}`,
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
const zod = countZodCopies(options.zodRoot ?? process.cwd());
|
|
166
|
-
const enabled = settings.getSetting("native_registration_enabled", "0") === "1";
|
|
167
|
-
return {
|
|
168
|
-
host: {
|
|
169
|
-
plugin_version: host.pluginVersion,
|
|
170
|
-
flavour: host.flavour,
|
|
171
|
-
shell_available: host.hasShell,
|
|
172
|
-
v2: { skill: host.v2.skill, reference: host.v2.reference },
|
|
173
|
-
},
|
|
174
|
-
hooks: hooks.hooks,
|
|
175
|
-
dependencies: {
|
|
176
|
-
declared: ["@opencode-ai/plugin"],
|
|
177
|
-
zod_copies: zod.copies,
|
|
178
|
-
...(zod.note !== undefined ? { note: zod.note } : {}),
|
|
179
|
-
},
|
|
180
|
-
native: {
|
|
181
|
-
enabled,
|
|
182
|
-
registered: {
|
|
183
|
-
skill: lastRegistration(store, "skill").registered,
|
|
184
|
-
reference: lastRegistration(store, "reference").registered,
|
|
185
|
-
},
|
|
186
|
-
verified: {
|
|
187
|
-
skill: lastRegistration(store, "skill").verified,
|
|
188
|
-
reference: lastRegistration(store, "reference").verified,
|
|
189
|
-
},
|
|
190
|
-
},
|
|
191
|
-
verdict: verdict.verdict,
|
|
192
|
-
reason: verdict.reason,
|
|
193
|
-
...(breaches !== null ? { perf: { scopes_over_budget: breaches } } : {}),
|
|
194
|
-
partial: hooks.partial,
|
|
195
|
-
};
|
|
196
|
-
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import type { MemoryService } from "./MemoryService.js";
|
|
2
|
-
import type { RepoTruth } from "./RepoTruth.js";
|
|
3
|
-
import type { Store } from "./Store.js";
|
|
4
|
-
/**
|
|
5
|
-
* v0.7.0 (K7-009 / plan §5.7) — `kevin_facts`.
|
|
6
|
-
*
|
|
7
|
-
* The user-facing answer to "why did Kevin stop suggesting that?". Lists the
|
|
8
|
-
* repository facts the current project was scanned for and every memory that
|
|
9
|
-
* was de-ranked by a contradiction, with its `truth_penalty`, `contradicted_at`
|
|
10
|
-
* and the human-readable reasons. Pure read apart from the optional re-scan. No
|
|
11
|
-
* LLM, no network, no hot-path cost.
|
|
12
|
-
*/
|
|
13
|
-
export interface KevinFactsDeps {
|
|
14
|
-
store: Store;
|
|
15
|
-
memoryService: MemoryService;
|
|
16
|
-
repoTruth: RepoTruth;
|
|
17
|
-
projectId: string;
|
|
18
|
-
}
|
|
19
|
-
export interface PenalizedMemory {
|
|
20
|
-
id: string;
|
|
21
|
-
type: string;
|
|
22
|
-
truth_penalty: number;
|
|
23
|
-
contradicted_at: string | null;
|
|
24
|
-
reasons: string[];
|
|
25
|
-
}
|
|
26
|
-
export interface KevinFactsResult {
|
|
27
|
-
project_id: string;
|
|
28
|
-
scanned_at: string | null;
|
|
29
|
-
truncated: {
|
|
30
|
-
is_truncated: true;
|
|
31
|
-
count: number;
|
|
32
|
-
} | {
|
|
33
|
-
is_truncated: false;
|
|
34
|
-
};
|
|
35
|
-
facts: {
|
|
36
|
-
file: string;
|
|
37
|
-
key_path: string;
|
|
38
|
-
value: string;
|
|
39
|
-
}[];
|
|
40
|
-
penalized: PenalizedMemory[];
|
|
41
|
-
}
|
|
42
|
-
export declare function buildKevinFacts(deps: KevinFactsDeps, refresh: boolean): KevinFactsResult;
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
export function buildKevinFacts(deps, refresh) {
|
|
2
|
-
if (refresh)
|
|
3
|
-
deps.repoTruth.scan();
|
|
4
|
-
// Penalized memories: scoped to the project, only those with a penalty.
|
|
5
|
-
const rows = deps.store
|
|
6
|
-
.prepare(`SELECT id, type, truth_penalty, contradicted_at
|
|
7
|
-
FROM memories
|
|
8
|
-
WHERE project_id = ? AND truth_penalty > 0
|
|
9
|
-
ORDER BY created_at`)
|
|
10
|
-
.all(deps.projectId);
|
|
11
|
-
const penalized = rows.map((r) => {
|
|
12
|
-
const mem = deps.memoryService.getById(r.id);
|
|
13
|
-
return {
|
|
14
|
-
id: r.id,
|
|
15
|
-
type: r.type,
|
|
16
|
-
truth_penalty: r.truth_penalty,
|
|
17
|
-
contradicted_at: r.contradicted_at,
|
|
18
|
-
reasons: mem ? deps.repoTruth.contradictions(mem) : [],
|
|
19
|
-
};
|
|
20
|
-
});
|
|
21
|
-
const stored = deps.repoTruth.facts();
|
|
22
|
-
const truncatedFact = stored.find((f) => f.keyPath === "_truncated");
|
|
23
|
-
const facts = stored
|
|
24
|
-
.filter((f) => f.keyPath !== "_truncated")
|
|
25
|
-
.map((f) => ({ file: f.file, key_path: f.keyPath, value: f.value }));
|
|
26
|
-
const scanned_at = deps.repoTruth.scannedAt();
|
|
27
|
-
return {
|
|
28
|
-
project_id: deps.projectId,
|
|
29
|
-
scanned_at,
|
|
30
|
-
truncated: truncatedFact
|
|
31
|
-
? { is_truncated: true, count: Number(truncatedFact.value) || 0 }
|
|
32
|
-
: { is_truncated: false },
|
|
33
|
-
facts,
|
|
34
|
-
penalized,
|
|
35
|
-
};
|
|
36
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import type { MemoryService } from "./MemoryService.js";
|
|
2
|
-
import type { SharedLayer } from "./SharedLayer.js";
|
|
3
|
-
import type { Store } from "./Store.js";
|
|
4
|
-
import type { Metrics } from "./metrics.js";
|
|
5
|
-
export interface ForgetInput {
|
|
6
|
-
ids: string[];
|
|
7
|
-
confirm?: boolean;
|
|
8
|
-
}
|
|
9
|
-
export interface Deps {
|
|
10
|
-
store: Store;
|
|
11
|
-
memoryService: MemoryService;
|
|
12
|
-
sharedLayer: SharedLayer;
|
|
13
|
-
okfPath: string;
|
|
14
|
-
metrics: Metrics;
|
|
15
|
-
}
|
|
16
|
-
export interface ForgetResult {
|
|
17
|
-
action: "forget";
|
|
18
|
-
ok: boolean;
|
|
19
|
-
dry_run: boolean;
|
|
20
|
-
per_id: Array<{
|
|
21
|
-
id: string;
|
|
22
|
-
archived: boolean;
|
|
23
|
-
reason?: string;
|
|
24
|
-
tombstone?: {
|
|
25
|
-
entry_id: string;
|
|
26
|
-
planned: boolean;
|
|
27
|
-
applied: boolean;
|
|
28
|
-
};
|
|
29
|
-
}>;
|
|
30
|
-
noop?: boolean;
|
|
31
|
-
reason?: string;
|
|
32
|
-
}
|
|
33
|
-
export declare function handleForget(input: ForgetInput, deps: Deps): ForgetResult;
|