@jmtrin/opencode-kevin 1.0.0 → 1.2.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 +54 -8
- 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/012_v11_drift.sql +24 -0
- package/dist/plugin/Archiver.js +2 -17
- package/dist/plugin/CausalChain.js +32 -13
- package/dist/plugin/ChatBridge.d.ts +41 -0
- package/dist/plugin/ChatBridge.js +103 -0
- package/dist/plugin/ConflictDetector.js +7 -29
- package/dist/plugin/DashboardHtml.d.ts +5 -0
- package/dist/plugin/DashboardHtml.js +180 -0
- package/dist/plugin/Feedback.js +2 -19
- package/dist/plugin/HookLiveness.d.ts +1 -0
- package/dist/plugin/HookLiveness.js +11 -27
- package/dist/plugin/InjectionLedger.js +104 -57
- package/dist/plugin/Materializer.js +1 -88
- package/dist/plugin/MemoryService.d.ts +59 -1
- package/dist/plugin/MemoryService.js +13 -106
- package/dist/plugin/Migrate.js +5 -0
- package/dist/plugin/Retrospective.js +7 -0
- package/dist/plugin/ToolCallObserver.js +18 -5
- package/dist/plugin/TuiActions.d.ts +43 -0
- package/dist/plugin/TuiActions.js +181 -0
- package/dist/plugin/TuiSnapshots.d.ts +24 -0
- package/dist/plugin/TuiSnapshots.js +158 -0
- package/dist/plugin/capabilities.d.ts +2 -0
- package/dist/plugin/capabilities.js +3 -0
- package/dist/plugin/columns.d.ts +11 -0
- package/dist/plugin/columns.js +54 -0
- package/dist/plugin/contract.d.ts +8 -0
- package/dist/plugin/contract.js +23 -5
- package/dist/plugin/index.d.ts +2 -2
- package/dist/plugin/index.js +315 -10
- package/dist/plugin/kevin_audit.d.ts +17 -1
- package/dist/plugin/kevin_audit.js +69 -1
- package/dist/plugin/kevin_forget.d.ts +33 -0
- package/dist/plugin/kevin_forget.js +260 -0
- package/dist/plugin/kevin_why.js +1 -18
- package/dist/plugin/metrics.d.ts +1 -1
- package/dist/plugin/metrics.js +8 -0
- package/dist/plugin/query-tokenizer.js +56 -8
- package/dist/plugin/time-ms.d.ts +1 -0
- package/dist/plugin/time-ms.js +16 -0
- package/dist/plugin/tui-types.d.ts +59 -0
- package/dist/plugin/tui-types.js +4 -0
- package/dist/plugin/tui.d.ts +18 -0
- package/dist/plugin/tui.js +198 -0
- package/package.json +8 -2
|
@@ -1,145 +1,145 @@
|
|
|
1
|
-
-- ============================================================================
|
|
2
|
-
-- 007_v06_pull.sql — v0.6.0 "Pull"
|
|
3
|
-
--
|
|
4
|
-
-- Distribution: curated artifacts instead of a per-prompt token tax.
|
|
5
|
-
--
|
|
6
|
-
-- Section 1: curation_proposals — the persisted human decision record.
|
|
7
|
-
-- Section 2: artifact_writes — the disk audit trail, including refusals.
|
|
8
|
-
-- Section 3: memories curation + inferability columns.
|
|
9
|
-
-- Section 4: metric seeds.
|
|
10
|
-
-- Section 5: setting seeds.
|
|
11
|
-
-- Section 6: conditional push-budget demotion.
|
|
12
|
-
-- Section 7: schema_version.
|
|
13
|
-
--
|
|
14
|
-
-- Additive only. Every CHECK constraint introduced here is on a NEW table;
|
|
15
|
-
-- no existing constraint is widened, so unlike migration 006 there is no
|
|
16
|
-
-- table rebuild in this file.
|
|
17
|
-
-- ============================================================================
|
|
18
|
-
|
|
19
|
-
-- ---------------------------------------------------------------------------
|
|
20
|
-
-- 1. curation_proposals — one row per proposed artifact change.
|
|
21
|
-
--
|
|
22
|
-
-- This table is the "explicit human decision between generation and
|
|
23
|
-
-- application" of Principle 22. `kevin_propose` writes 'pending' rows and
|
|
24
|
-
-- nothing else; `kevin_approve` moves them to 'approved' → 'applied' or to
|
|
25
|
-
-- 'rejected'. Rows are never deleted: rejection history is the evidence
|
|
26
|
-
-- base for roadmap kill criterion K4 ("proposals rejected more often than
|
|
27
|
-
-- approved"), which is uncheckable if rejections are discarded.
|
|
28
|
-
--
|
|
29
|
-
-- memory_id has no REFERENCES clause. Store sets PRAGMA foreign_keys = ON,
|
|
30
|
-
-- and a hard FK would block deleting a memory that a historical proposal
|
|
31
|
-
-- once mentioned — the same reasoning as superseded_by in migration 006.
|
|
32
|
-
-- ---------------------------------------------------------------------------
|
|
33
|
-
CREATE TABLE IF NOT EXISTS curation_proposals (
|
|
34
|
-
id TEXT PRIMARY KEY,
|
|
35
|
-
project_id TEXT NOT NULL,
|
|
36
|
-
memory_id TEXT NOT NULL,
|
|
37
|
-
kind TEXT NOT NULL CHECK (kind IN ('agents_md','skill','reference')),
|
|
38
|
-
target_path TEXT NOT NULL,
|
|
39
|
-
proposed_text TEXT NOT NULL,
|
|
40
|
-
diff TEXT NOT NULL,
|
|
41
|
-
status TEXT NOT NULL DEFAULT 'pending'
|
|
42
|
-
CHECK (status IN ('pending','approved','rejected','applied','superseded')),
|
|
43
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
44
|
-
decided_at TEXT,
|
|
45
|
-
applied_at TEXT
|
|
46
|
-
);
|
|
47
|
-
CREATE INDEX IF NOT EXISTS idx_proposals_status ON curation_proposals(status);
|
|
48
|
-
CREATE INDEX IF NOT EXISTS idx_proposals_memory ON curation_proposals(memory_id);
|
|
49
|
-
CREATE INDEX IF NOT EXISTS idx_proposals_project ON curation_proposals(project_id);
|
|
50
|
-
|
|
51
|
-
-- ---------------------------------------------------------------------------
|
|
52
|
-
-- 2. artifact_writes — the append-only audit trail for every disk operation.
|
|
53
|
-
--
|
|
54
|
-
-- A row is written for EVERY ArtifactWriter.apply() call, including
|
|
55
|
-
-- outcome='noop' and outcome='refused'. A refusal that leaves no trace is
|
|
56
|
-
-- indistinguishable from a write that never happened, and the whole point
|
|
57
|
-
-- of §5.1 rule 3 is that a refusal is a reportable event.
|
|
58
|
-
--
|
|
59
|
-
-- hash_before / hash_after are SHA-256 of the full file contents, not of
|
|
60
|
-
-- the marker block. That is what makes rule 4 (bytes outside the markers
|
|
61
|
-
-- are byte-identical) auditable after the fact rather than only at test
|
|
62
|
-
-- time.
|
|
63
|
-
-- ---------------------------------------------------------------------------
|
|
64
|
-
CREATE TABLE IF NOT EXISTS artifact_writes (
|
|
65
|
-
id TEXT PRIMARY KEY,
|
|
66
|
-
proposal_id TEXT,
|
|
67
|
-
project_id TEXT NOT NULL,
|
|
68
|
-
path TEXT NOT NULL,
|
|
69
|
-
bytes_before INTEGER,
|
|
70
|
-
bytes_after INTEGER,
|
|
71
|
-
hash_before TEXT,
|
|
72
|
-
hash_after TEXT,
|
|
73
|
-
outcome TEXT NOT NULL CHECK (outcome IN ('written','noop','refused')),
|
|
74
|
-
reason TEXT,
|
|
75
|
-
wrote_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
76
|
-
);
|
|
77
|
-
CREATE INDEX IF NOT EXISTS idx_artifact_writes_path ON artifact_writes(path);
|
|
78
|
-
|
|
79
|
-
-- ---------------------------------------------------------------------------
|
|
80
|
-
-- 3. memories: curation state and inferability.
|
|
81
|
-
--
|
|
82
|
-
-- curated / curated_at record that a memory has already been published,
|
|
83
|
-
-- so the Curator does not re-propose it on every session idle.
|
|
84
|
-
--
|
|
85
|
-
-- inferable is deliberately NULLABLE with no default. Three states are
|
|
86
|
-
-- needed, not two: 1 = inferable (a self-describing diagnostic the model
|
|
87
|
-
-- resolves for free), 0 = non-inferable (project truth), NULL = unknown
|
|
88
|
-
-- (not yet classified, or classified as 'unknown'). The Curator predicate
|
|
89
|
-
-- is `inferable != 1`, so NULL rows stay eligible — an unclassified memory
|
|
90
|
-
-- must not be silently withheld from curation.
|
|
91
|
-
-- ---------------------------------------------------------------------------
|
|
92
|
-
ALTER TABLE memories ADD COLUMN curated INTEGER NOT NULL DEFAULT 0;
|
|
93
|
-
ALTER TABLE memories ADD COLUMN curated_at TEXT;
|
|
94
|
-
ALTER TABLE memories ADD COLUMN inferable INTEGER;
|
|
95
|
-
|
|
96
|
-
CREATE INDEX IF NOT EXISTS idx_memories_curated ON memories(curated);
|
|
97
|
-
CREATE INDEX IF NOT EXISTS idx_memories_inferable ON memories(inferable);
|
|
98
|
-
|
|
99
|
-
-- ---------------------------------------------------------------------------
|
|
100
|
-
-- 4. Metric seeds. Order matches the additions to METRIC_KEYS in metrics.ts.
|
|
101
|
-
-- injections_blocked_confidence is the sixth member of the v0.5 blocked
|
|
102
|
-
-- family and MUST be counted like the other five (Principle 16).
|
|
103
|
-
-- ---------------------------------------------------------------------------
|
|
104
|
-
INSERT OR IGNORE INTO kevin_metrics (key, value) VALUES
|
|
105
|
-
('proposals_created', 0),
|
|
106
|
-
('proposals_approved', 0),
|
|
107
|
-
('proposals_rejected', 0),
|
|
108
|
-
('artifact_writes_total', 0),
|
|
109
|
-
('artifact_writes_noop', 0),
|
|
110
|
-
('injections_blocked_confidence', 0);
|
|
111
|
-
|
|
112
|
-
-- ---------------------------------------------------------------------------
|
|
113
|
-
-- 5. Setting seeds. Values are TEXT, always. Read them with an explicit
|
|
114
|
-
-- string comparison or an explicit Number() parse — never `=== 1`.
|
|
115
|
-
-- (That exact mistake kept cross_project_enabled unreachable for the
|
|
116
|
-
-- whole of v0.3.0.)
|
|
117
|
-
--
|
|
118
|
-
-- skill_emission_enabled and reference_emission_enabled default to '0':
|
|
119
|
-
-- the pull channels ship OFF and are opted into, because they depend on a
|
|
120
|
-
-- v2 domain Kevin does not pin.
|
|
121
|
-
-- ---------------------------------------------------------------------------
|
|
122
|
-
INSERT OR IGNORE INTO kevin_settings (key, value) VALUES
|
|
123
|
-
('curation_enabled', '1'),
|
|
124
|
-
('agents_md_path', 'AGENTS.md'),
|
|
125
|
-
('skill_emission_enabled', '0'),
|
|
126
|
-
('reference_emission_enabled', '0'),
|
|
127
|
-
('injection_confidence_floor', '0.6');
|
|
128
|
-
|
|
129
|
-
-- ---------------------------------------------------------------------------
|
|
130
|
-
-- 6. Push-budget demotion.
|
|
131
|
-
--
|
|
132
|
-
-- Lower the default push budget only where the user has not overridden it.
|
|
133
|
-
-- A user who deliberately set 1200 (or 1500, or 200) keeps their value;
|
|
134
|
-
-- only an installation still sitting on the v0.5 default of '900' is
|
|
135
|
-
-- moved to '400'. An unconditional UPDATE here would silently discard a
|
|
136
|
-
-- deliberate configuration choice, which is a worse defect than the token
|
|
137
|
-
-- cost it would save.
|
|
138
|
-
-- ---------------------------------------------------------------------------
|
|
139
|
-
UPDATE kevin_settings SET value = '400'
|
|
140
|
-
WHERE key = 'pre_prompt_budget_tokens' AND value = '900';
|
|
141
|
-
|
|
142
|
-
-- ---------------------------------------------------------------------------
|
|
143
|
-
-- 7. Version marker.
|
|
144
|
-
-- ---------------------------------------------------------------------------
|
|
1
|
+
-- ============================================================================
|
|
2
|
+
-- 007_v06_pull.sql — v0.6.0 "Pull"
|
|
3
|
+
--
|
|
4
|
+
-- Distribution: curated artifacts instead of a per-prompt token tax.
|
|
5
|
+
--
|
|
6
|
+
-- Section 1: curation_proposals — the persisted human decision record.
|
|
7
|
+
-- Section 2: artifact_writes — the disk audit trail, including refusals.
|
|
8
|
+
-- Section 3: memories curation + inferability columns.
|
|
9
|
+
-- Section 4: metric seeds.
|
|
10
|
+
-- Section 5: setting seeds.
|
|
11
|
+
-- Section 6: conditional push-budget demotion.
|
|
12
|
+
-- Section 7: schema_version.
|
|
13
|
+
--
|
|
14
|
+
-- Additive only. Every CHECK constraint introduced here is on a NEW table;
|
|
15
|
+
-- no existing constraint is widened, so unlike migration 006 there is no
|
|
16
|
+
-- table rebuild in this file.
|
|
17
|
+
-- ============================================================================
|
|
18
|
+
|
|
19
|
+
-- ---------------------------------------------------------------------------
|
|
20
|
+
-- 1. curation_proposals — one row per proposed artifact change.
|
|
21
|
+
--
|
|
22
|
+
-- This table is the "explicit human decision between generation and
|
|
23
|
+
-- application" of Principle 22. `kevin_propose` writes 'pending' rows and
|
|
24
|
+
-- nothing else; `kevin_approve` moves them to 'approved' → 'applied' or to
|
|
25
|
+
-- 'rejected'. Rows are never deleted: rejection history is the evidence
|
|
26
|
+
-- base for roadmap kill criterion K4 ("proposals rejected more often than
|
|
27
|
+
-- approved"), which is uncheckable if rejections are discarded.
|
|
28
|
+
--
|
|
29
|
+
-- memory_id has no REFERENCES clause. Store sets PRAGMA foreign_keys = ON,
|
|
30
|
+
-- and a hard FK would block deleting a memory that a historical proposal
|
|
31
|
+
-- once mentioned — the same reasoning as superseded_by in migration 006.
|
|
32
|
+
-- ---------------------------------------------------------------------------
|
|
33
|
+
CREATE TABLE IF NOT EXISTS curation_proposals (
|
|
34
|
+
id TEXT PRIMARY KEY,
|
|
35
|
+
project_id TEXT NOT NULL,
|
|
36
|
+
memory_id TEXT NOT NULL,
|
|
37
|
+
kind TEXT NOT NULL CHECK (kind IN ('agents_md','skill','reference')),
|
|
38
|
+
target_path TEXT NOT NULL,
|
|
39
|
+
proposed_text TEXT NOT NULL,
|
|
40
|
+
diff TEXT NOT NULL,
|
|
41
|
+
status TEXT NOT NULL DEFAULT 'pending'
|
|
42
|
+
CHECK (status IN ('pending','approved','rejected','applied','superseded')),
|
|
43
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
44
|
+
decided_at TEXT,
|
|
45
|
+
applied_at TEXT
|
|
46
|
+
);
|
|
47
|
+
CREATE INDEX IF NOT EXISTS idx_proposals_status ON curation_proposals(status);
|
|
48
|
+
CREATE INDEX IF NOT EXISTS idx_proposals_memory ON curation_proposals(memory_id);
|
|
49
|
+
CREATE INDEX IF NOT EXISTS idx_proposals_project ON curation_proposals(project_id);
|
|
50
|
+
|
|
51
|
+
-- ---------------------------------------------------------------------------
|
|
52
|
+
-- 2. artifact_writes — the append-only audit trail for every disk operation.
|
|
53
|
+
--
|
|
54
|
+
-- A row is written for EVERY ArtifactWriter.apply() call, including
|
|
55
|
+
-- outcome='noop' and outcome='refused'. A refusal that leaves no trace is
|
|
56
|
+
-- indistinguishable from a write that never happened, and the whole point
|
|
57
|
+
-- of §5.1 rule 3 is that a refusal is a reportable event.
|
|
58
|
+
--
|
|
59
|
+
-- hash_before / hash_after are SHA-256 of the full file contents, not of
|
|
60
|
+
-- the marker block. That is what makes rule 4 (bytes outside the markers
|
|
61
|
+
-- are byte-identical) auditable after the fact rather than only at test
|
|
62
|
+
-- time.
|
|
63
|
+
-- ---------------------------------------------------------------------------
|
|
64
|
+
CREATE TABLE IF NOT EXISTS artifact_writes (
|
|
65
|
+
id TEXT PRIMARY KEY,
|
|
66
|
+
proposal_id TEXT,
|
|
67
|
+
project_id TEXT NOT NULL,
|
|
68
|
+
path TEXT NOT NULL,
|
|
69
|
+
bytes_before INTEGER,
|
|
70
|
+
bytes_after INTEGER,
|
|
71
|
+
hash_before TEXT,
|
|
72
|
+
hash_after TEXT,
|
|
73
|
+
outcome TEXT NOT NULL CHECK (outcome IN ('written','noop','refused')),
|
|
74
|
+
reason TEXT,
|
|
75
|
+
wrote_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
76
|
+
);
|
|
77
|
+
CREATE INDEX IF NOT EXISTS idx_artifact_writes_path ON artifact_writes(path);
|
|
78
|
+
|
|
79
|
+
-- ---------------------------------------------------------------------------
|
|
80
|
+
-- 3. memories: curation state and inferability.
|
|
81
|
+
--
|
|
82
|
+
-- curated / curated_at record that a memory has already been published,
|
|
83
|
+
-- so the Curator does not re-propose it on every session idle.
|
|
84
|
+
--
|
|
85
|
+
-- inferable is deliberately NULLABLE with no default. Three states are
|
|
86
|
+
-- needed, not two: 1 = inferable (a self-describing diagnostic the model
|
|
87
|
+
-- resolves for free), 0 = non-inferable (project truth), NULL = unknown
|
|
88
|
+
-- (not yet classified, or classified as 'unknown'). The Curator predicate
|
|
89
|
+
-- is `inferable != 1`, so NULL rows stay eligible — an unclassified memory
|
|
90
|
+
-- must not be silently withheld from curation.
|
|
91
|
+
-- ---------------------------------------------------------------------------
|
|
92
|
+
ALTER TABLE memories ADD COLUMN curated INTEGER NOT NULL DEFAULT 0;
|
|
93
|
+
ALTER TABLE memories ADD COLUMN curated_at TEXT;
|
|
94
|
+
ALTER TABLE memories ADD COLUMN inferable INTEGER;
|
|
95
|
+
|
|
96
|
+
CREATE INDEX IF NOT EXISTS idx_memories_curated ON memories(curated);
|
|
97
|
+
CREATE INDEX IF NOT EXISTS idx_memories_inferable ON memories(inferable);
|
|
98
|
+
|
|
99
|
+
-- ---------------------------------------------------------------------------
|
|
100
|
+
-- 4. Metric seeds. Order matches the additions to METRIC_KEYS in metrics.ts.
|
|
101
|
+
-- injections_blocked_confidence is the sixth member of the v0.5 blocked
|
|
102
|
+
-- family and MUST be counted like the other five (Principle 16).
|
|
103
|
+
-- ---------------------------------------------------------------------------
|
|
104
|
+
INSERT OR IGNORE INTO kevin_metrics (key, value) VALUES
|
|
105
|
+
('proposals_created', 0),
|
|
106
|
+
('proposals_approved', 0),
|
|
107
|
+
('proposals_rejected', 0),
|
|
108
|
+
('artifact_writes_total', 0),
|
|
109
|
+
('artifact_writes_noop', 0),
|
|
110
|
+
('injections_blocked_confidence', 0);
|
|
111
|
+
|
|
112
|
+
-- ---------------------------------------------------------------------------
|
|
113
|
+
-- 5. Setting seeds. Values are TEXT, always. Read them with an explicit
|
|
114
|
+
-- string comparison or an explicit Number() parse — never `=== 1`.
|
|
115
|
+
-- (That exact mistake kept cross_project_enabled unreachable for the
|
|
116
|
+
-- whole of v0.3.0.)
|
|
117
|
+
--
|
|
118
|
+
-- skill_emission_enabled and reference_emission_enabled default to '0':
|
|
119
|
+
-- the pull channels ship OFF and are opted into, because they depend on a
|
|
120
|
+
-- v2 domain Kevin does not pin.
|
|
121
|
+
-- ---------------------------------------------------------------------------
|
|
122
|
+
INSERT OR IGNORE INTO kevin_settings (key, value) VALUES
|
|
123
|
+
('curation_enabled', '1'),
|
|
124
|
+
('agents_md_path', 'AGENTS.md'),
|
|
125
|
+
('skill_emission_enabled', '0'),
|
|
126
|
+
('reference_emission_enabled', '0'),
|
|
127
|
+
('injection_confidence_floor', '0.6');
|
|
128
|
+
|
|
129
|
+
-- ---------------------------------------------------------------------------
|
|
130
|
+
-- 6. Push-budget demotion.
|
|
131
|
+
--
|
|
132
|
+
-- Lower the default push budget only where the user has not overridden it.
|
|
133
|
+
-- A user who deliberately set 1200 (or 1500, or 200) keeps their value;
|
|
134
|
+
-- only an installation still sitting on the v0.5 default of '900' is
|
|
135
|
+
-- moved to '400'. An unconditional UPDATE here would silently discard a
|
|
136
|
+
-- deliberate configuration choice, which is a worse defect than the token
|
|
137
|
+
-- cost it would save.
|
|
138
|
+
-- ---------------------------------------------------------------------------
|
|
139
|
+
UPDATE kevin_settings SET value = '400'
|
|
140
|
+
WHERE key = 'pre_prompt_budget_tokens' AND value = '900';
|
|
141
|
+
|
|
142
|
+
-- ---------------------------------------------------------------------------
|
|
143
|
+
-- 7. Version marker.
|
|
144
|
+
-- ---------------------------------------------------------------------------
|
|
145
145
|
INSERT OR IGNORE INTO schema_version (version) VALUES ('007');
|
|
@@ -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,
|
|
@@ -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'
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type PendingProposal } from "./TuiActions.js";
|
|
2
|
+
export type BridgeDeps = {
|
|
3
|
+
readonly getPending: () => readonly PendingProposal[];
|
|
4
|
+
readonly approve: (proposalId: string) => unknown;
|
|
5
|
+
readonly reject: (proposalId: string, note?: string) => unknown;
|
|
6
|
+
readonly acknowledge: (conflictId: string) => unknown;
|
|
7
|
+
readonly metrics?: {
|
|
8
|
+
incr: (key: "tui_actions_invoked", by?: number) => void;
|
|
9
|
+
} | null;
|
|
10
|
+
};
|
|
11
|
+
export type ParsedBridgeCommand = {
|
|
12
|
+
readonly type: "approve";
|
|
13
|
+
readonly proposalId: string;
|
|
14
|
+
readonly token: string;
|
|
15
|
+
readonly note?: string;
|
|
16
|
+
} | {
|
|
17
|
+
readonly type: "reject";
|
|
18
|
+
readonly proposalId: string;
|
|
19
|
+
readonly token: string;
|
|
20
|
+
readonly note?: string;
|
|
21
|
+
} | {
|
|
22
|
+
readonly type: "ack";
|
|
23
|
+
readonly conflictId: string;
|
|
24
|
+
};
|
|
25
|
+
export declare function parseBridgeCommand(text: string): ParsedBridgeCommand | null;
|
|
26
|
+
export declare const KEVIN_COMMAND_RE: RegExp;
|
|
27
|
+
export interface BridgeResult {
|
|
28
|
+
readonly handled: boolean;
|
|
29
|
+
readonly status?: "applied" | "rejected" | "stale_skipped" | "error";
|
|
30
|
+
readonly detail?: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Execute a chat message through the bridge.
|
|
34
|
+
* - Non-matching → {handled:false} (byte-identical pass-through)
|
|
35
|
+
* - Matching but stale/invalid → {handled:false, status:"stale_skipped"} (pass-through + counter)
|
|
36
|
+
* - Valid → executes via deps handlers, {handled:true}
|
|
37
|
+
*
|
|
38
|
+
* Valid commands are SWALLOWED — caller must not forward to model.
|
|
39
|
+
* Invalid/stale commands pass through untouched (D12-09).
|
|
40
|
+
*/
|
|
41
|
+
export declare function handleBridgeCommand(text: string, deps: BridgeDeps): BridgeResult;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// v1.2.0 (K12-018 / plan §4.4 R3, D12-09) — chat-command bridge (universal actions).
|
|
2
|
+
// Hot path: ONE regex test on non-match, allocation-free.
|
|
3
|
+
import { verifyFresh, } from "./TuiActions.js";
|
|
4
|
+
// Exact regex for approve/reject (require 16-hex token, optional note capture).
|
|
5
|
+
// Ack variant is token-free (acknowledge is non-destructive, matches mailbox).
|
|
6
|
+
const APPROVE_REJECT_RE = /^\/kevin-(approve|reject)\s+(\S+)\s+([0-9a-f]{16})(?:\s+([\s\S]+))?$/;
|
|
7
|
+
const ACK_RE = /^\/kevin-ack\s+(\S+)\s*$/;
|
|
8
|
+
// Combined pattern for documentation / static analysis (covers both forms).
|
|
9
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
10
|
+
const _DOC_RE = /^\/kevin-(approve|reject|ack)\s+(\S+)\s+([0-9a-f]{16})(?:\s+([\s\S]+))?$/;
|
|
11
|
+
export function parseBridgeCommand(text) {
|
|
12
|
+
// Approve / reject — require token
|
|
13
|
+
const m = APPROVE_REJECT_RE.exec(text);
|
|
14
|
+
if (m) {
|
|
15
|
+
const type = m[1];
|
|
16
|
+
const proposalId = m[2];
|
|
17
|
+
const token = m[3];
|
|
18
|
+
const note = m[4] !== undefined ? m[4] : undefined;
|
|
19
|
+
if (type === "approve")
|
|
20
|
+
return {
|
|
21
|
+
type: "approve",
|
|
22
|
+
proposalId,
|
|
23
|
+
token,
|
|
24
|
+
...(note !== undefined ? { note } : {}),
|
|
25
|
+
};
|
|
26
|
+
return {
|
|
27
|
+
type: "reject",
|
|
28
|
+
proposalId,
|
|
29
|
+
token,
|
|
30
|
+
...(note !== undefined ? { note } : {}),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
const ack = ACK_RE.exec(text);
|
|
34
|
+
if (ack) {
|
|
35
|
+
return { type: "ack", conflictId: ack[1] };
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
// Backward alias for tests that import by plan name
|
|
40
|
+
export const KEVIN_COMMAND_RE = APPROVE_REJECT_RE;
|
|
41
|
+
/**
|
|
42
|
+
* Execute a chat message through the bridge.
|
|
43
|
+
* - Non-matching → {handled:false} (byte-identical pass-through)
|
|
44
|
+
* - Matching but stale/invalid → {handled:false, status:"stale_skipped"} (pass-through + counter)
|
|
45
|
+
* - Valid → executes via deps handlers, {handled:true}
|
|
46
|
+
*
|
|
47
|
+
* Valid commands are SWALLOWED — caller must not forward to model.
|
|
48
|
+
* Invalid/stale commands pass through untouched (D12-09).
|
|
49
|
+
*/
|
|
50
|
+
export function handleBridgeCommand(text, deps) {
|
|
51
|
+
const parsed = parseBridgeCommand(text);
|
|
52
|
+
if (!parsed)
|
|
53
|
+
return { handled: false };
|
|
54
|
+
if (parsed.type === "ack") {
|
|
55
|
+
// Ack is non-destructive, no token verification — matches mailbox acknowledge semantics.
|
|
56
|
+
try {
|
|
57
|
+
deps.acknowledge(parsed.conflictId);
|
|
58
|
+
try {
|
|
59
|
+
deps.metrics?.incr("tui_actions_invoked", 1);
|
|
60
|
+
}
|
|
61
|
+
catch { }
|
|
62
|
+
return { handled: true, status: "applied" };
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
66
|
+
return { handled: true, status: "error", detail: msg };
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// approve / reject — verify token against CURRENT pending state (same as mailbox)
|
|
70
|
+
const pending = deps.getPending();
|
|
71
|
+
const action = {
|
|
72
|
+
type: parsed.type,
|
|
73
|
+
proposalId: parsed.proposalId,
|
|
74
|
+
token: parsed.token,
|
|
75
|
+
...(parsed.note !== undefined ? { note: parsed.note } : {}),
|
|
76
|
+
};
|
|
77
|
+
const fresh = verifyFresh(action, pending);
|
|
78
|
+
if (!fresh.ok) {
|
|
79
|
+
// Invalid/stale → pass-through untouched (do NOT swallow). Caller should forward byte-identically.
|
|
80
|
+
// Audit counter could be incremented here, but not as contract metric (blockedSnapshot-style internal).
|
|
81
|
+
return { handled: false, status: "stale_skipped", detail: fresh.reason };
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
if (parsed.type === "approve") {
|
|
85
|
+
deps.approve(parsed.proposalId);
|
|
86
|
+
try {
|
|
87
|
+
deps.metrics?.incr("tui_actions_invoked", 1);
|
|
88
|
+
}
|
|
89
|
+
catch { }
|
|
90
|
+
return { handled: true, status: "applied" };
|
|
91
|
+
}
|
|
92
|
+
deps.reject(parsed.proposalId, parsed.note);
|
|
93
|
+
try {
|
|
94
|
+
deps.metrics?.incr("tui_actions_invoked", 1);
|
|
95
|
+
}
|
|
96
|
+
catch { }
|
|
97
|
+
return { handled: true, status: "rejected" };
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
101
|
+
return { handled: true, status: "error", detail: msg };
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -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() {
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { TuiSnapshotSet } from "./tui-types.js";
|
|
2
|
+
export declare function escapeHtml(text: string): string;
|
|
3
|
+
export declare function proposalToken(proposalId: string, proposedText: string): string;
|
|
4
|
+
export declare function renderDashboard(views: TuiSnapshotSet): string;
|
|
5
|
+
export declare function writeDashboard(root: string, views: TuiSnapshotSet): string;
|