@jmtrin/opencode-kevin 0.4.0 → 0.6.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 +580 -360
- package/dist/migrations/006_v05_glassbox.sql +118 -0
- package/dist/migrations/007_v06_pull.sql +145 -0
- package/dist/plugin/Archiver.d.ts +42 -0
- package/dist/plugin/Archiver.js +99 -0
- package/dist/plugin/Archiver.js.map +1 -0
- package/dist/plugin/ArtifactWriter.d.ts +50 -0
- package/dist/plugin/ArtifactWriter.js +240 -0
- package/dist/plugin/ArtifactWriter.js.map +1 -0
- package/dist/plugin/ContextInjector.d.ts +87 -0
- package/dist/plugin/ContextInjector.js +212 -44
- package/dist/plugin/ContextInjector.js.map +1 -1
- package/dist/plugin/Curator.d.ts +96 -0
- package/dist/plugin/Curator.js +252 -0
- package/dist/plugin/Curator.js.map +1 -0
- package/dist/plugin/Feedback.d.ts +67 -0
- package/dist/plugin/Feedback.js +138 -0
- package/dist/plugin/Feedback.js.map +1 -0
- package/dist/plugin/InjectionLedger.d.ts +9 -2
- package/dist/plugin/InjectionLedger.js +63 -9
- package/dist/plugin/InjectionLedger.js.map +1 -1
- package/dist/plugin/Materializer.d.ts +59 -0
- package/dist/plugin/Materializer.js +237 -0
- package/dist/plugin/Materializer.js.map +1 -0
- package/dist/plugin/MemoryService.d.ts +38 -0
- package/dist/plugin/MemoryService.js +265 -30
- package/dist/plugin/MemoryService.js.map +1 -1
- package/dist/plugin/Migrate.js +41 -0
- package/dist/plugin/Migrate.js.map +1 -1
- package/dist/plugin/QualityGate.d.ts +53 -0
- package/dist/plugin/QualityGate.js +50 -8
- package/dist/plugin/QualityGate.js.map +1 -1
- package/dist/plugin/Retrospective.d.ts +1 -0
- package/dist/plugin/Retrospective.js +24 -1
- package/dist/plugin/Retrospective.js.map +1 -1
- package/dist/plugin/capabilities.d.ts +15 -0
- package/dist/plugin/capabilities.js +42 -0
- package/dist/plugin/capabilities.js.map +1 -0
- package/dist/plugin/confidence.d.ts +3 -1
- package/dist/plugin/confidence.js +14 -2
- package/dist/plugin/confidence.js.map +1 -1
- package/dist/plugin/diff.d.ts +8 -0
- package/dist/plugin/diff.js +183 -0
- package/dist/plugin/diff.js.map +1 -0
- package/dist/plugin/index.d.ts +3 -1
- package/dist/plugin/index.js +371 -0
- package/dist/plugin/index.js.map +1 -1
- package/dist/plugin/inferability.d.ts +32 -0
- package/dist/plugin/inferability.js +89 -0
- package/dist/plugin/inferability.js.map +1 -0
- package/dist/plugin/kevin_approve.d.ts +34 -0
- package/dist/plugin/kevin_approve.js +50 -0
- package/dist/plugin/kevin_approve.js.map +1 -0
- package/dist/plugin/kevin_audit.d.ts +95 -0
- package/dist/plugin/kevin_audit.js +233 -0
- package/dist/plugin/kevin_audit.js.map +1 -0
- package/dist/plugin/kevin_propose.d.ts +23 -0
- package/dist/plugin/kevin_propose.js +15 -0
- package/dist/plugin/kevin_propose.js.map +1 -0
- package/dist/plugin/kevin_publish.d.ts +38 -0
- package/dist/plugin/kevin_publish.js +19 -0
- package/dist/plugin/kevin_publish.js.map +1 -0
- package/dist/plugin/kevin_why.js +23 -2
- package/dist/plugin/kevin_why.js.map +1 -1
- package/dist/plugin/metrics.d.ts +32 -1
- package/dist/plugin/metrics.js +86 -2
- package/dist/plugin/metrics.js.map +1 -1
- package/dist/plugin/replay-types.d.ts +327 -0
- package/dist/plugin/replay-types.js +65 -0
- package/dist/plugin/replay-types.js.map +1 -0
- package/dist/plugin/replay.d.ts +36 -0
- package/dist/plugin/replay.js +203 -0
- package/dist/plugin/replay.js.map +1 -0
- package/migrations/006_v05_glassbox.sql +118 -0
- package/migrations/007_v06_pull.sql +145 -0
- package/package.json +3 -2
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
-- ============================================================================
|
|
2
|
+
-- 006_v05_glassbox.sql — v0.5.0 "Glass Box"
|
|
3
|
+
--
|
|
4
|
+
-- Honest measurement, human feedback, lifecycle completion.
|
|
5
|
+
--
|
|
6
|
+
-- Section 1: rebuild kevin_injections to admit a fourth outcome.
|
|
7
|
+
-- Section 2: human feedback storage.
|
|
8
|
+
-- Section 3: memory lifecycle columns.
|
|
9
|
+
-- Section 4: metric seeds.
|
|
10
|
+
-- Section 5: setting seeds.
|
|
11
|
+
-- Section 6: schema_version.
|
|
12
|
+
-- ============================================================================
|
|
13
|
+
|
|
14
|
+
-- ---------------------------------------------------------------------------
|
|
15
|
+
-- 1. kevin_injections: add 'inconclusive'.
|
|
16
|
+
--
|
|
17
|
+
-- SQLite cannot ALTER a CHECK constraint, so the table must be rebuilt.
|
|
18
|
+
-- Migration 004 set this precedent. kevin_injections has no FTS5 triggers,
|
|
19
|
+
-- so unlike 004 this is a straight four-step rebuild.
|
|
20
|
+
--
|
|
21
|
+
-- Existing rows with outcome='effective' are remapped to 'inconclusive'.
|
|
22
|
+
-- This is not data loss: v0.4's 'effective' meant "the error did not recur",
|
|
23
|
+
-- which is the exact definition of the new 'inconclusive' bucket. Rows that
|
|
24
|
+
-- genuinely earned the new 'effective' will be re-settled naturally, and the
|
|
25
|
+
-- post-apply hook re-derives the counters from the table.
|
|
26
|
+
-- ---------------------------------------------------------------------------
|
|
27
|
+
CREATE TABLE IF NOT EXISTS kevin_injections_new (
|
|
28
|
+
id TEXT PRIMARY KEY,
|
|
29
|
+
memory_id TEXT NOT NULL,
|
|
30
|
+
fingerprint TEXT NOT NULL,
|
|
31
|
+
session_id TEXT NOT NULL,
|
|
32
|
+
hook TEXT NOT NULL CHECK (hook IN ('pre_prompt','compacting')),
|
|
33
|
+
tokens INTEGER NOT NULL,
|
|
34
|
+
injected_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
35
|
+
outcome TEXT NOT NULL DEFAULT 'unmeasured'
|
|
36
|
+
CHECK (outcome IN ('unmeasured','effective','ineffective','inconclusive'))
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
INSERT INTO kevin_injections_new
|
|
40
|
+
(id, memory_id, fingerprint, session_id, hook, tokens, injected_at, outcome)
|
|
41
|
+
SELECT
|
|
42
|
+
id, memory_id, fingerprint, session_id, hook, tokens, injected_at,
|
|
43
|
+
CASE WHEN outcome = 'effective' THEN 'inconclusive' ELSE outcome END
|
|
44
|
+
FROM kevin_injections;
|
|
45
|
+
|
|
46
|
+
DROP TABLE kevin_injections;
|
|
47
|
+
ALTER TABLE kevin_injections_new RENAME TO kevin_injections;
|
|
48
|
+
|
|
49
|
+
CREATE INDEX IF NOT EXISTS idx_injections_fp ON kevin_injections(fingerprint);
|
|
50
|
+
CREATE INDEX IF NOT EXISTS idx_injections_session ON kevin_injections(session_id);
|
|
51
|
+
CREATE INDEX IF NOT EXISTS idx_injections_outcome ON kevin_injections(outcome);
|
|
52
|
+
|
|
53
|
+
-- ---------------------------------------------------------------------------
|
|
54
|
+
-- 2. Human feedback. Append-only audit trail; the hot path reads the
|
|
55
|
+
-- denormalized counters on `memories` (section 3), never this table.
|
|
56
|
+
-- ---------------------------------------------------------------------------
|
|
57
|
+
CREATE TABLE IF NOT EXISTS memory_feedback (
|
|
58
|
+
id TEXT PRIMARY KEY,
|
|
59
|
+
memory_id TEXT NOT NULL,
|
|
60
|
+
verdict TEXT NOT NULL CHECK (verdict IN ('useful','wrong','outdated','ignore')),
|
|
61
|
+
session_id TEXT,
|
|
62
|
+
note TEXT,
|
|
63
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
CREATE INDEX IF NOT EXISTS idx_feedback_memory ON memory_feedback(memory_id);
|
|
67
|
+
CREATE INDEX IF NOT EXISTS idx_feedback_created ON memory_feedback(created_at);
|
|
68
|
+
|
|
69
|
+
-- ---------------------------------------------------------------------------
|
|
70
|
+
-- 3. Memory lifecycle and feedback columns.
|
|
71
|
+
--
|
|
72
|
+
-- feedback_positive / feedback_negative are SEPARATE from evidence_count
|
|
73
|
+
-- and recurrence_count by design: human judgement is evidence about the
|
|
74
|
+
-- memory, causal counters are evidence about the world. Mixing them was
|
|
75
|
+
-- the confidence-poisoning defect closed in v0.4.0.
|
|
76
|
+
--
|
|
77
|
+
-- superseded_by has no REFERENCES clause on purpose. Store enables
|
|
78
|
+
-- PRAGMA foreign_keys=ON, and a hard FK would block deletion of a memory
|
|
79
|
+
-- that superseded another.
|
|
80
|
+
-- ---------------------------------------------------------------------------
|
|
81
|
+
ALTER TABLE memories ADD COLUMN feedback_positive INTEGER NOT NULL DEFAULT 0;
|
|
82
|
+
ALTER TABLE memories ADD COLUMN feedback_negative INTEGER NOT NULL DEFAULT 0;
|
|
83
|
+
ALTER TABLE memories ADD COLUMN ignored INTEGER NOT NULL DEFAULT 0;
|
|
84
|
+
ALTER TABLE memories ADD COLUMN superseded_by TEXT;
|
|
85
|
+
ALTER TABLE memories ADD COLUMN archived_at TEXT;
|
|
86
|
+
|
|
87
|
+
CREATE INDEX IF NOT EXISTS idx_memories_ignored ON memories(ignored);
|
|
88
|
+
CREATE INDEX IF NOT EXISTS idx_memories_archived ON memories(archived_at);
|
|
89
|
+
|
|
90
|
+
-- ---------------------------------------------------------------------------
|
|
91
|
+
-- 4. Metric seeds. Order matches the additions to METRIC_KEYS in metrics.ts.
|
|
92
|
+
-- ---------------------------------------------------------------------------
|
|
93
|
+
INSERT OR IGNORE INTO kevin_metrics (key, value) VALUES
|
|
94
|
+
('injections_inconclusive', 0),
|
|
95
|
+
('injections_blocked_seen', 0),
|
|
96
|
+
('injections_blocked_weak', 0),
|
|
97
|
+
('injections_blocked_recurrence',0),
|
|
98
|
+
('injections_blocked_stale', 0),
|
|
99
|
+
('injections_blocked_ignored', 0),
|
|
100
|
+
('feedback_positive_total', 0),
|
|
101
|
+
('feedback_negative_total', 0),
|
|
102
|
+
('memories_archived', 0);
|
|
103
|
+
|
|
104
|
+
-- ---------------------------------------------------------------------------
|
|
105
|
+
-- 5. Setting seeds. Values are TEXT, always. Read them with an explicit
|
|
106
|
+
-- string comparison or an explicit Number() parse — never `=== 1`.
|
|
107
|
+
-- (That exact mistake kept cross_project_enabled unreachable for the
|
|
108
|
+
-- whole of v0.3.0.)
|
|
109
|
+
-- ---------------------------------------------------------------------------
|
|
110
|
+
INSERT OR IGNORE INTO kevin_settings (key, value) VALUES
|
|
111
|
+
('deterministic_retrieval', '0'),
|
|
112
|
+
('pre_prompt_budget_tokens', '900'),
|
|
113
|
+
('archive_after_days', '30');
|
|
114
|
+
|
|
115
|
+
-- ---------------------------------------------------------------------------
|
|
116
|
+
-- 6. Version marker.
|
|
117
|
+
-- ---------------------------------------------------------------------------
|
|
118
|
+
INSERT OR IGNORE INTO schema_version (version) VALUES ('006');
|
|
@@ -0,0 +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
|
+
-- ---------------------------------------------------------------------------
|
|
145
|
+
INSERT OR IGNORE INTO schema_version (version) VALUES ('007');
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { MemoryService } from "./MemoryService.js";
|
|
2
|
+
import type { Store } from "./Store.js";
|
|
3
|
+
import type { Metrics } from "./metrics.js";
|
|
4
|
+
/**
|
|
5
|
+
* v0.5.0 Archiver (K5-012 / plan §5.4, D5-05).
|
|
6
|
+
*
|
|
7
|
+
* Lifecycle tail of the memory loop: reflectors mark a lesson `stale` when
|
|
8
|
+
* its fingerprint recurred enough times (penalizeRecurringReflectors,
|
|
9
|
+
* migration 003), but nothing ever retires those rows — they keep
|
|
10
|
+
* circulating through status filters and inflating counts. The Archiver
|
|
11
|
+
* runs on `session.idle` and retires stale memories whose last activity
|
|
12
|
+
* (`updated_at`, falling back to `last_verified_at`) is older than
|
|
13
|
+
* `archive_after_days` (kevin_settings, seeded '30' in migration 006).
|
|
14
|
+
*
|
|
15
|
+
* Rules:
|
|
16
|
+
* - Only `status = 'stale'` rows with `archived_at IS NULL`.
|
|
17
|
+
* - `type != 'pattern'`: a pattern is the FIXED form of a fingerprint
|
|
18
|
+
* (K4-025); archiving it would break kevin_why and the recurrence
|
|
19
|
+
* re-admission path (QualityGate rule 4).
|
|
20
|
+
* - Clock: the injection clock (K5-008) — tests freeze it; production
|
|
21
|
+
* defaults to `new Date()`. Never `Date.now()` inside.
|
|
22
|
+
* - `memories_archived` is bumped by the batch size, once per run.
|
|
23
|
+
* - Pre-006 DBs (no `archived_at`) degrade to a no-op.
|
|
24
|
+
*
|
|
25
|
+
* Archived rows remain queryable with `includeSuperseded: true`-style
|
|
26
|
+
* reads (status filter is exclusive, not destructive).
|
|
27
|
+
*/
|
|
28
|
+
export declare class Archiver {
|
|
29
|
+
private readonly store;
|
|
30
|
+
private readonly memoryService;
|
|
31
|
+
private readonly metrics;
|
|
32
|
+
private readonly now;
|
|
33
|
+
constructor(store: Store, memoryService: MemoryService, metrics?: Metrics | null, now?: () => Date);
|
|
34
|
+
/**
|
|
35
|
+
* Archives every eligible stale memory. Returns the number of rows
|
|
36
|
+
* retired. Idempotent: archived rows are excluded by `archived_at IS
|
|
37
|
+
* NULL`, so a second run archives nothing.
|
|
38
|
+
*/
|
|
39
|
+
run(): number;
|
|
40
|
+
private archiveAfterDays;
|
|
41
|
+
private hasArchivedColumn;
|
|
42
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v0.5.0 Archiver (K5-012 / plan §5.4, D5-05).
|
|
3
|
+
*
|
|
4
|
+
* Lifecycle tail of the memory loop: reflectors mark a lesson `stale` when
|
|
5
|
+
* its fingerprint recurred enough times (penalizeRecurringReflectors,
|
|
6
|
+
* migration 003), but nothing ever retires those rows — they keep
|
|
7
|
+
* circulating through status filters and inflating counts. The Archiver
|
|
8
|
+
* runs on `session.idle` and retires stale memories whose last activity
|
|
9
|
+
* (`updated_at`, falling back to `last_verified_at`) is older than
|
|
10
|
+
* `archive_after_days` (kevin_settings, seeded '30' in migration 006).
|
|
11
|
+
*
|
|
12
|
+
* Rules:
|
|
13
|
+
* - Only `status = 'stale'` rows with `archived_at IS NULL`.
|
|
14
|
+
* - `type != 'pattern'`: a pattern is the FIXED form of a fingerprint
|
|
15
|
+
* (K4-025); archiving it would break kevin_why and the recurrence
|
|
16
|
+
* re-admission path (QualityGate rule 4).
|
|
17
|
+
* - Clock: the injection clock (K5-008) — tests freeze it; production
|
|
18
|
+
* defaults to `new Date()`. Never `Date.now()` inside.
|
|
19
|
+
* - `memories_archived` is bumped by the batch size, once per run.
|
|
20
|
+
* - Pre-006 DBs (no `archived_at`) degrade to a no-op.
|
|
21
|
+
*
|
|
22
|
+
* Archived rows remain queryable with `includeSuperseded: true`-style
|
|
23
|
+
* reads (status filter is exclusive, not destructive).
|
|
24
|
+
*/
|
|
25
|
+
export class Archiver {
|
|
26
|
+
store;
|
|
27
|
+
memoryService;
|
|
28
|
+
metrics;
|
|
29
|
+
now;
|
|
30
|
+
constructor(store, memoryService, metrics, now = () => new Date()) {
|
|
31
|
+
this.store = store;
|
|
32
|
+
this.memoryService = memoryService;
|
|
33
|
+
this.metrics = metrics ?? null;
|
|
34
|
+
this.now = now;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Archives every eligible stale memory. Returns the number of rows
|
|
38
|
+
* retired. Idempotent: archived rows are excluded by `archived_at IS
|
|
39
|
+
* NULL`, so a second run archives nothing.
|
|
40
|
+
*/
|
|
41
|
+
run() {
|
|
42
|
+
if (!this.hasArchivedColumn())
|
|
43
|
+
return 0;
|
|
44
|
+
const days = this.archiveAfterDays();
|
|
45
|
+
if (days <= 0)
|
|
46
|
+
return 0;
|
|
47
|
+
const now = this.now();
|
|
48
|
+
const cutoff = formatSqliteUtc(new Date(now.getTime() - days * 86_400_000));
|
|
49
|
+
this.store
|
|
50
|
+
.prepare(`UPDATE memories
|
|
51
|
+
SET status = 'archived', archived_at = ?, updated_at = ?
|
|
52
|
+
WHERE status = 'stale'
|
|
53
|
+
AND type != 'pattern'
|
|
54
|
+
AND archived_at IS NULL
|
|
55
|
+
AND updated_at < ?`)
|
|
56
|
+
.run(formatSqliteUtc(now), formatSqliteUtc(now), cutoff);
|
|
57
|
+
const changed = this.store.prepare("SELECT changes() AS n").get();
|
|
58
|
+
const n = changed.n;
|
|
59
|
+
if (n > 0)
|
|
60
|
+
this.metrics?.incr("memories_archived", n);
|
|
61
|
+
return n;
|
|
62
|
+
}
|
|
63
|
+
archiveAfterDays() {
|
|
64
|
+
try {
|
|
65
|
+
const raw = this.memoryService.getSetting("archive_after_days", "30");
|
|
66
|
+
const n = Number(raw);
|
|
67
|
+
return Number.isFinite(n) ? n : 30;
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return 30;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
hasArchivedColumn() {
|
|
74
|
+
return hasArchivedColumnCached(this.store);
|
|
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;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/** 'YYYY-MM-DD HH:MM:SS' UTC — the format SQLite `datetime('now')` uses,
|
|
94
|
+
* so lexicographic comparisons against `updated_at` are valid. */
|
|
95
|
+
function formatSqliteUtc(d) {
|
|
96
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
97
|
+
return `${d.getUTCFullYear()}-${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())} ${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}:${pad(d.getUTCSeconds())}`;
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=Archiver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Archiver.js","sourceRoot":"","sources":["../../plugin/Archiver.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { Store } from "./Store.js";
|
|
2
|
+
import type { Metrics } from "./metrics.js";
|
|
3
|
+
export declare const MARKER_BEGIN = "<!-- kevin:begin \u2014 curated by opencode-kevin, safe to edit -->";
|
|
4
|
+
export declare const MARKER_END = "<!-- kevin:end -->";
|
|
5
|
+
export type WriteOutcome = "written" | "noop" | "refused";
|
|
6
|
+
export interface WritePlan {
|
|
7
|
+
readonly path: string;
|
|
8
|
+
readonly before: string;
|
|
9
|
+
readonly after: string;
|
|
10
|
+
readonly diff: string;
|
|
11
|
+
readonly outcome: WriteOutcome;
|
|
12
|
+
readonly reason?: string;
|
|
13
|
+
readonly hashBefore: string;
|
|
14
|
+
readonly hashAfter: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Rule 9 (K6-009 / plan §5.1, D6-02) — three layers applied to the body
|
|
18
|
+
* before splicing:
|
|
19
|
+
* (a) the escaping discipline of `plugin/memory-format.ts`;
|
|
20
|
+
* (b) strip any line containing `kevin:begin` or `kevin:end`, in any
|
|
21
|
+
* casing, anywhere in the line;
|
|
22
|
+
* (c) strip HTML comment terminators (`-->`).
|
|
23
|
+
* Without this, a memory containing a literal `<!-- kevin:end -->` line would
|
|
24
|
+
* close the marker comment early and let subsequent content escape the
|
|
25
|
+
* curated region on the next regeneration — a marker-injection variant of the
|
|
26
|
+
* v0.1.5 prompt-injection defect (plan §3.5).
|
|
27
|
+
*/
|
|
28
|
+
export declare function sanitizeArtifactBody(body: string): string;
|
|
29
|
+
/**
|
|
30
|
+
* v0.6.0 (K6-005 / plan §5.1) — the single write path to disk (D6-01).
|
|
31
|
+
*
|
|
32
|
+
* `plan()` is pure: it reads the target file, locates the marker pair, splices
|
|
33
|
+
* the body between the markers and returns a {@link WritePlan}. It performs no
|
|
34
|
+
* writes — rule 1. `apply()` is implemented by K6-007; until then it is a stub
|
|
35
|
+
* that throws, so no caller can accidentally write before the audit trail
|
|
36
|
+
* exists.
|
|
37
|
+
*
|
|
38
|
+
* `projectId` is a constructor argument rather than a per-call argument so that
|
|
39
|
+
* every audit row is attributed without the call site having to remember.
|
|
40
|
+
*/
|
|
41
|
+
export declare class ArtifactWriter {
|
|
42
|
+
private readonly store;
|
|
43
|
+
private readonly projectId;
|
|
44
|
+
private readonly metrics;
|
|
45
|
+
constructor(store: Store, projectId: string, metrics?: Metrics | null);
|
|
46
|
+
plan(path: string, body: string): WritePlan;
|
|
47
|
+
apply(plan: WritePlan, proposalId?: string): WriteOutcome;
|
|
48
|
+
private renameTemp;
|
|
49
|
+
private audit;
|
|
50
|
+
}
|