@jmtrin/opencode-kevin 1.2.0 → 1.4.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.
Files changed (140) hide show
  1. package/README.md +84 -22
  2. package/dist/plugin/index.d.ts +5 -4
  3. package/dist/plugin/index.js +127 -113
  4. package/dist/plugin/native.d.ts +2 -2
  5. package/dist/plugin/native.js +2 -2
  6. package/package.json +56 -68
  7. package/dist/migrations/001_initial.sql +0 -92
  8. package/dist/migrations/002_indexes.sql +0 -14
  9. package/dist/migrations/003_v02_signal.sql +0 -58
  10. package/dist/migrations/004_v03_knowledge.sql +0 -138
  11. package/dist/migrations/005_v04_signal.sql +0 -57
  12. package/dist/migrations/006_v05_glassbox.sql +0 -118
  13. package/dist/migrations/007_v06_pull.sql +0 -145
  14. package/dist/migrations/008_v07_truth.sql +0 -124
  15. package/dist/migrations/009_v08_team.sql +0 -100
  16. package/dist/migrations/010_v09_native.sql +0 -78
  17. package/dist/migrations/011_v10_proven.sql +0 -69
  18. package/dist/migrations/012_v11_drift.sql +0 -24
  19. package/dist/plugin/Archiver.d.ts +0 -42
  20. package/dist/plugin/Archiver.js +0 -83
  21. package/dist/plugin/ArtifactWriter.d.ts +0 -93
  22. package/dist/plugin/ArtifactWriter.js +0 -291
  23. package/dist/plugin/CausalChain.d.ts +0 -22
  24. package/dist/plugin/CausalChain.js +0 -197
  25. package/dist/plugin/ChatBridge.d.ts +0 -41
  26. package/dist/plugin/ChatBridge.js +0 -103
  27. package/dist/plugin/ConflictDetector.d.ts +0 -35
  28. package/dist/plugin/ConflictDetector.js +0 -260
  29. package/dist/plugin/ContextInjector.d.ts +0 -222
  30. package/dist/plugin/ContextInjector.js +0 -533
  31. package/dist/plugin/ConventionMiner.d.ts +0 -35
  32. package/dist/plugin/ConventionMiner.js +0 -242
  33. package/dist/plugin/Curator.d.ts +0 -116
  34. package/dist/plugin/Curator.js +0 -324
  35. package/dist/plugin/DashboardHtml.d.ts +0 -5
  36. package/dist/plugin/DashboardHtml.js +0 -180
  37. package/dist/plugin/Feedback.d.ts +0 -67
  38. package/dist/plugin/Feedback.js +0 -120
  39. package/dist/plugin/HookLiveness.d.ts +0 -88
  40. package/dist/plugin/HookLiveness.js +0 -322
  41. package/dist/plugin/InjectionLedger.d.ts +0 -98
  42. package/dist/plugin/InjectionLedger.js +0 -295
  43. package/dist/plugin/LessonFixer.d.ts +0 -44
  44. package/dist/plugin/LessonFixer.js +0 -45
  45. package/dist/plugin/Materializer.d.ts +0 -83
  46. package/dist/plugin/Materializer.js +0 -181
  47. package/dist/plugin/MemoryService.d.ts +0 -360
  48. package/dist/plugin/MemoryService.js +0 -1242
  49. package/dist/plugin/Migrate.d.ts +0 -17
  50. package/dist/plugin/Migrate.js +0 -271
  51. package/dist/plugin/PatternMiner.d.ts +0 -49
  52. package/dist/plugin/PatternMiner.js +0 -132
  53. package/dist/plugin/QualityGate.d.ts +0 -124
  54. package/dist/plugin/QualityGate.js +0 -119
  55. package/dist/plugin/Reflector.d.ts +0 -91
  56. package/dist/plugin/Reflector.js +0 -307
  57. package/dist/plugin/RepoIdentity.d.ts +0 -124
  58. package/dist/plugin/RepoIdentity.js +0 -300
  59. package/dist/plugin/RepoTruth.d.ts +0 -80
  60. package/dist/plugin/RepoTruth.js +0 -599
  61. package/dist/plugin/Retrospective.d.ts +0 -17
  62. package/dist/plugin/Retrospective.js +0 -216
  63. package/dist/plugin/SharedLayer.d.ts +0 -159
  64. package/dist/plugin/SharedLayer.js +0 -462
  65. package/dist/plugin/Store.d.ts +0 -29
  66. package/dist/plugin/Store.js +0 -50
  67. package/dist/plugin/ToolCallObserver.d.ts +0 -32
  68. package/dist/plugin/ToolCallObserver.js +0 -201
  69. package/dist/plugin/TuiActions.d.ts +0 -43
  70. package/dist/plugin/TuiActions.js +0 -181
  71. package/dist/plugin/TuiSnapshots.d.ts +0 -24
  72. package/dist/plugin/TuiSnapshots.js +0 -158
  73. package/dist/plugin/columns.d.ts +0 -11
  74. package/dist/plugin/columns.js +0 -54
  75. package/dist/plugin/confidence.d.ts +0 -8
  76. package/dist/plugin/confidence.js +0 -34
  77. package/dist/plugin/contract.d.ts +0 -54
  78. package/dist/plugin/contract.js +0 -386
  79. package/dist/plugin/diff.d.ts +0 -8
  80. package/dist/plugin/diff.js +0 -182
  81. package/dist/plugin/escape.d.ts +0 -39
  82. package/dist/plugin/escape.js +0 -60
  83. package/dist/plugin/fingerprint.d.ts +0 -27
  84. package/dist/plugin/fingerprint.js +0 -73
  85. package/dist/plugin/inferability.d.ts +0 -32
  86. package/dist/plugin/inferability.js +0 -88
  87. package/dist/plugin/kevin_approve.d.ts +0 -34
  88. package/dist/plugin/kevin_approve.js +0 -52
  89. package/dist/plugin/kevin_audit.d.ts +0 -229
  90. package/dist/plugin/kevin_audit.js +0 -631
  91. package/dist/plugin/kevin_bench.d.ts +0 -10
  92. package/dist/plugin/kevin_bench.js +0 -82
  93. package/dist/plugin/kevin_conflicts.d.ts +0 -9
  94. package/dist/plugin/kevin_conflicts.js +0 -50
  95. package/dist/plugin/kevin_contract.d.ts +0 -8
  96. package/dist/plugin/kevin_contract.js +0 -59
  97. package/dist/plugin/kevin_doctor.d.ts +0 -62
  98. package/dist/plugin/kevin_doctor.js +0 -196
  99. package/dist/plugin/kevin_facts.d.ts +0 -42
  100. package/dist/plugin/kevin_facts.js +0 -36
  101. package/dist/plugin/kevin_forget.d.ts +0 -33
  102. package/dist/plugin/kevin_forget.js +0 -260
  103. package/dist/plugin/kevin_native.d.ts +0 -29
  104. package/dist/plugin/kevin_native.js +0 -79
  105. package/dist/plugin/kevin_propose.d.ts +0 -23
  106. package/dist/plugin/kevin_propose.js +0 -14
  107. package/dist/plugin/kevin_publish.d.ts +0 -38
  108. package/dist/plugin/kevin_publish.js +0 -18
  109. package/dist/plugin/kevin_why.d.ts +0 -23
  110. package/dist/plugin/kevin_why.js +0 -111
  111. package/dist/plugin/memory-format.d.ts +0 -20
  112. package/dist/plugin/memory-format.js +0 -59
  113. package/dist/plugin/metrics.d.ts +0 -97
  114. package/dist/plugin/metrics.js +0 -288
  115. package/dist/plugin/okf-export.d.ts +0 -3
  116. package/dist/plugin/okf-export.js +0 -134
  117. package/dist/plugin/okf-import.d.ts +0 -76
  118. package/dist/plugin/okf-import.js +0 -271
  119. package/dist/plugin/okf.d.ts +0 -107
  120. package/dist/plugin/okf.js +0 -303
  121. package/dist/plugin/perf.d.ts +0 -35
  122. package/dist/plugin/perf.js +0 -174
  123. package/dist/plugin/query-tokenizer.d.ts +0 -13
  124. package/dist/plugin/query-tokenizer.js +0 -133
  125. package/dist/plugin/redact.d.ts +0 -2
  126. package/dist/plugin/redact.js +0 -18
  127. package/dist/plugin/replay-types.d.ts +0 -69
  128. package/dist/plugin/replay-types.js +0 -156
  129. package/dist/plugin/replay.d.ts +0 -36
  130. package/dist/plugin/replay.js +0 -202
  131. package/dist/plugin/sqlite-adapter.d.ts +0 -12
  132. package/dist/plugin/sqlite-adapter.js +0 -81
  133. package/dist/plugin/time-ms.d.ts +0 -1
  134. package/dist/plugin/time-ms.js +0 -16
  135. package/dist/plugin/tui-types.d.ts +0 -59
  136. package/dist/plugin/tui-types.js +0 -4
  137. package/dist/plugin/tui.d.ts +0 -18
  138. package/dist/plugin/tui.js +0 -198
  139. package/dist/plugin/uuid.d.ts +0 -1
  140. package/dist/plugin/uuid.js +0 -50
@@ -1,201 +0,0 @@
1
- import { hasColumn } from "./columns.js";
2
- import { fingerprint as computeFingerprint } from "./fingerprint.js";
3
- import { redactPaths, stripPrivate } from "./redact.js";
4
- import { uuidv7 } from "./uuid.js";
5
- const SECRET_PATTERNS = [
6
- /\b(API_KEY|SECRET|PASSWORD|TOKEN)\b\s*[=:]\s*\S+/gi,
7
- /\bBearer\s+\S+/gi,
8
- // BUG-013 — the old `\btoken\s+\S+/gi` mangled harmless phrasing
9
- // ("token budget", "token count", "max token limit") that LLM tools
10
- // emit routinely. Narrowed to credential contexts: named token
11
- // variables WITH an assignment, and `token=<value>` / `token:
12
- // <value>` assignments.
13
- /\b(access_?token|auth_?token|api_?token)\b\s*[=:]\s*\S+/gi,
14
- /\btoken\s*[=:]\s*\S+/gi,
15
- ];
16
- export class ToolCallObserver {
17
- store;
18
- startTs = new Map();
19
- metrics;
20
- constructor(store, metrics) {
21
- this.store = store;
22
- this.metrics = metrics ?? null;
23
- }
24
- onBefore(input, _output) {
25
- const key = this.key(input);
26
- this.startTs.set(key, Date.now());
27
- }
28
- onAfter(input, output) {
29
- const key = this.key(input);
30
- const start = this.startTs.get(key) ?? Date.now();
31
- this.startTs.delete(key);
32
- const durationMs = Math.max(0, Date.now() - start);
33
- const sessionId = input.sessionId ?? "unknown";
34
- const argsSummary = this.summarizeArgs(input.args ?? {});
35
- const success = output.success === true ? 1 : 0;
36
- const agent = input.agent ?? null;
37
- const stderr = stripPrivate(output.stderr ?? "");
38
- const stdout = stripPrivate(output.stdout ?? "");
39
- const errorType = output.success === false
40
- ? this.inferErrorType(stderr, stdout, output.exitCode)
41
- : null;
42
- const metadata = JSON.stringify(this.redactArgs(input.args ?? {}));
43
- const projectId = input.projectId ?? null;
44
- const fp = computeFingerprint(`${input.tool}|${argsSummary}|${success}`, projectId ?? undefined);
45
- if (this.isDedupEnabled()) {
46
- const existing = this.store
47
- .prepare(`SELECT 1 FROM tool_calls
48
- WHERE fingerprint = ?
49
- AND (project_id IS ? OR (project_id IS NULL AND ? IS NULL))
50
- AND strftime('%Y-%m-%d %H:%M', ts) = strftime('%Y-%m-%d %H:%M', 'now')
51
- LIMIT 1`)
52
- .get(fp, projectId, projectId);
53
- if (existing) {
54
- this.metrics?.incr("tool_calls_deduped", 1);
55
- return;
56
- }
57
- }
58
- // v1.1.0 (K11-002 / plan §5.2, D11-01/D11-07) — dual-write: legacy `ts`
59
- // stays (`datetime('now')`), new `ts_ms` holds Date.now() when the
60
- // column exists. Probe is cached via columns registry (K11-011).
61
- if (hasColumn(this.store, "tool_calls", "ts_ms")) {
62
- this.store
63
- .prepare(`INSERT INTO tool_calls
64
- (id, session_id, ts, ts_ms, tool, args_summary, success, duration_ms, agent, error_type, metadata, project_id, fingerprint)
65
- VALUES (?, ?, datetime('now'), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
66
- .run(input.callID ?? uuidv7(), sessionId, Date.now(), input.tool, argsSummary, success, durationMs, agent, errorType, metadata, projectId, fp);
67
- }
68
- else {
69
- this.store
70
- .prepare(`INSERT INTO tool_calls
71
- (id, session_id, ts, tool, args_summary, success, duration_ms, agent, error_type, metadata, project_id, fingerprint)
72
- VALUES (?, ?, datetime('now'), ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
73
- .run(input.callID ?? uuidv7(), sessionId, input.tool, argsSummary, success, durationMs, agent, errorType, metadata, projectId, fp);
74
- }
75
- }
76
- isDedupEnabled() {
77
- try {
78
- const row = this.store
79
- .prepare("SELECT value FROM kevin_settings WHERE key = 'tool_calls_dedup_enabled'")
80
- .get();
81
- return row?.value === "1";
82
- }
83
- catch {
84
- return false;
85
- }
86
- }
87
- redactSecrets(text) {
88
- let out = text;
89
- for (const pat of SECRET_PATTERNS) {
90
- out = out.replace(pat, (match) => {
91
- // BUG-013 — preserve the original separator so
92
- // `auth_token = abc123` keeps its spacing: only the
93
- // value is replaced.
94
- const m = match.match(/^(.+?)(\s*[=:]\s*)(\S+)$/);
95
- if (m) {
96
- return `${m[1]}${m[2]}<redacted>`;
97
- }
98
- const parts = match.split(/\s+/);
99
- return `${parts[0]} <redacted>`;
100
- });
101
- }
102
- return out;
103
- }
104
- summarizeArgs(args) {
105
- const interestingKeys = [
106
- "filePath",
107
- "path",
108
- "cwd",
109
- "command",
110
- "cmd",
111
- "file",
112
- "directory",
113
- ];
114
- const parts = [];
115
- for (const [k, v] of Object.entries(args)) {
116
- if (interestingKeys.includes(k)) {
117
- parts.push(`${k}: ${this.redactValue(v)}`);
118
- }
119
- else if (this.looksSecret(k)) {
120
- parts.push(`${k}: <redacted>`);
121
- }
122
- else {
123
- parts.push(`${k}: ${this.redactValue(v, 200)}`);
124
- }
125
- }
126
- let summary = parts.join(", ");
127
- if (summary.length > 500)
128
- summary = `${summary.slice(0, 500)}...`;
129
- return summary;
130
- }
131
- inferErrorType(stderr, stdout, exitCode) {
132
- const combined = `${stderr}\n${stdout}`.toLowerCase();
133
- if (exitCode === 124 ||
134
- /timed out|timeout|etimedout|\bkilled\b|sigterm|sigkill/.test(combined)) {
135
- return "timeout";
136
- }
137
- if (/error ts|tsc|\btypescript\b/.test(combined))
138
- return "typecheck";
139
- if (/\b(lint|biome|eslint)\b/.test(combined))
140
- return "lint";
141
- if (/\b(fail|vitest|jest|test failed)\b/.test(combined))
142
- return "test";
143
- if (/error:|typeerror|referenceerror|syntaxerror/.test(combined))
144
- return "runtime";
145
- if ((exitCode === undefined || exitCode === -1) &&
146
- stderr.trim() === "" &&
147
- stdout.trim() === "") {
148
- return "timeout";
149
- }
150
- return "unknown";
151
- }
152
- key(input) {
153
- if (input.callID)
154
- return `callID::${input.callID}`;
155
- const argsKey = JSON.stringify(input.args ?? {});
156
- return `${input.sessionId ?? "unknown"}::${input.tool}::${argsKey}`;
157
- }
158
- looksSecret(key) {
159
- const k = key.toLowerCase();
160
- return /(key|secret|password|token|bearer)/.test(k);
161
- }
162
- redactArgs(args) {
163
- const out = {};
164
- for (const [k, v] of Object.entries(args)) {
165
- if (this.looksSecret(k)) {
166
- out[k] = "<redacted>";
167
- }
168
- else {
169
- out[k] = this.redactValue(v);
170
- }
171
- }
172
- return out;
173
- }
174
- redactValue(v, truncateAt) {
175
- if (typeof v === "string") {
176
- let s = stripPrivate(v);
177
- s = redactPaths(s);
178
- s = this.redactSecrets(s);
179
- if (truncateAt !== undefined && s.length > truncateAt) {
180
- s = `${s.slice(0, truncateAt)}...`;
181
- }
182
- return s;
183
- }
184
- if (Array.isArray(v)) {
185
- return v.map((item) => this.redactValue(item));
186
- }
187
- if (v && typeof v === "object") {
188
- const obj = {};
189
- for (const [k, val] of Object.entries(v)) {
190
- if (this.looksSecret(k)) {
191
- obj[k] = "<redacted>";
192
- }
193
- else {
194
- obj[k] = this.redactValue(val);
195
- }
196
- }
197
- return obj;
198
- }
199
- return v;
200
- }
201
- }
@@ -1,43 +0,0 @@
1
- import type { ActionResult, TuiAction } from "./tui-types.js";
2
- export interface MailboxReadResult {
3
- readonly actions: readonly TuiAction[];
4
- readonly warnings: readonly string[];
5
- }
6
- /**
7
- * Read `join(root,"tui","actions.json")` tolerant.
8
- * - missing file → {actions:[], warnings:[]}
9
- * - malformed JSON → {actions:[], warnings:["malformed_json"]}
10
- * - non-object or missing/non-array actions → {actions:[], warnings:["invalid_shape"]}
11
- * - unknown type values are dropped with warning per entry
12
- * Never deletes the file here.
13
- */
14
- export declare function readMailbox(root: string): MailboxReadResult;
15
- export declare function proposalToken(proposalId: string, proposedText: string): string;
16
- export interface PendingProposal {
17
- readonly id: string;
18
- readonly proposedText: string;
19
- }
20
- export declare function verifyFresh(action: TuiAction, currentPending: readonly PendingProposal[]): {
21
- ok: true;
22
- } | {
23
- ok: false;
24
- reason: string;
25
- };
26
- export type ActionStatus = ActionResult["status"];
27
- export interface ProcessDeps {
28
- readonly getPending: () => readonly PendingProposal[];
29
- readonly approve: (proposalId: string) => unknown;
30
- readonly reject: (proposalId: string, note?: string) => unknown;
31
- readonly acknowledge: (conflictId: string) => unknown;
32
- readonly metrics?: {
33
- incr: (key: "tui_actions_invoked", by?: number) => void;
34
- } | null;
35
- }
36
- export declare function processActions(actions: readonly TuiAction[], deps: ProcessDeps): ActionResult[];
37
- export declare function writeResults(root: string, results: readonly ActionResult[]): void;
38
- export declare function deleteMailbox(root: string): void;
39
- /**
40
- * Convenience: read → process → write results → delete queue.
41
- * Returns results (empty if no actions). Mirrors idle-chain usage.
42
- */
43
- export declare function consumeMailbox(root: string, deps: ProcessDeps): ActionResult[];
@@ -1,181 +0,0 @@
1
- // v1.2.0 (K12-005 / plan §4.3) — mailbox tolerant parser (phase F1).
2
- // v1.2.0 (K12-006 / plan §4.3, D12-04) — token scheme + stale detection
3
- import { createHash } from "node:crypto";
4
- import { mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync, } from "node:fs";
5
- import { join } from "node:path";
6
- function isRecord(v) {
7
- return typeof v === "object" && v !== null && !Array.isArray(v);
8
- }
9
- function isValidAction(raw) {
10
- if (!isRecord(raw))
11
- return null;
12
- const t = raw.type;
13
- if (t === "approve") {
14
- if (typeof raw.proposalId === "string" && typeof raw.token === "string") {
15
- return { type: "approve", proposalId: raw.proposalId, token: raw.token };
16
- }
17
- return null;
18
- }
19
- if (t === "reject") {
20
- if (typeof raw.proposalId === "string" && typeof raw.token === "string") {
21
- const note = typeof raw.note === "string" ? raw.note : undefined;
22
- return note !== undefined
23
- ? { type: "reject", proposalId: raw.proposalId, token: raw.token, note }
24
- : { type: "reject", proposalId: raw.proposalId, token: raw.token };
25
- }
26
- return null;
27
- }
28
- if (t === "acknowledge") {
29
- if (typeof raw.conflictId === "string") {
30
- return { type: "acknowledge", conflictId: raw.conflictId };
31
- }
32
- return null;
33
- }
34
- return null;
35
- }
36
- /**
37
- * Read `join(root,"tui","actions.json")` tolerant.
38
- * - missing file → {actions:[], warnings:[]}
39
- * - malformed JSON → {actions:[], warnings:["malformed_json"]}
40
- * - non-object or missing/non-array actions → {actions:[], warnings:["invalid_shape"]}
41
- * - unknown type values are dropped with warning per entry
42
- * Never deletes the file here.
43
- */
44
- export function readMailbox(root) {
45
- const path = join(root, "tui", "actions.json");
46
- let raw;
47
- try {
48
- raw = readFileSync(path, "utf8");
49
- }
50
- catch (err) {
51
- const code = err?.code;
52
- if (code === "ENOENT")
53
- return { actions: [], warnings: [] };
54
- return { actions: [], warnings: ["read_error"] };
55
- }
56
- let parsed;
57
- try {
58
- parsed = JSON.parse(raw);
59
- }
60
- catch {
61
- return { actions: [], warnings: ["malformed_json"] };
62
- }
63
- if (!isRecord(parsed)) {
64
- return { actions: [], warnings: ["invalid_shape"] };
65
- }
66
- const maybeActions = parsed.actions;
67
- if (!Array.isArray(maybeActions)) {
68
- return { actions: [], warnings: ["invalid_shape"] };
69
- }
70
- const actions = [];
71
- const warnings = [];
72
- for (const entry of maybeActions) {
73
- if (!isRecord(entry)) {
74
- warnings.push("dropped_invalid_entry");
75
- continue;
76
- }
77
- const type = entry.type;
78
- if (type !== "approve" && type !== "reject" && type !== "acknowledge") {
79
- warnings.push(`dropped_unknown_type:${String(type)}`);
80
- continue;
81
- }
82
- const valid = isValidAction(entry);
83
- if (!valid) {
84
- warnings.push(`dropped_invalid_${String(type)}`);
85
- continue;
86
- }
87
- actions.push(valid);
88
- }
89
- return { actions, warnings };
90
- }
91
- // v1.2.0 (K12-006 / D12-04) — first 16 hex of SHA-256(proposalId + "\0" + proposedText)
92
- export function proposalToken(proposalId, proposedText) {
93
- return createHash("sha256")
94
- .update(`${proposalId}\0${proposedText}`, "utf8")
95
- .digest("hex")
96
- .slice(0, 16);
97
- }
98
- export function verifyFresh(action, currentPending) {
99
- if (action.type === "acknowledge")
100
- return { ok: true };
101
- const pending = currentPending.find((p) => p.id === action.proposalId);
102
- if (!pending) {
103
- return { ok: false, reason: "content_changed_or_absent" };
104
- }
105
- const expected = proposalToken(pending.id, pending.proposedText);
106
- if (expected !== action.token) {
107
- return { ok: false, reason: "content_changed_or_absent" };
108
- }
109
- return { ok: true };
110
- }
111
- export function processActions(actions, deps) {
112
- const results = [];
113
- const pendingSnapshot = deps.getPending();
114
- for (const action of actions) {
115
- // Stale check for approve/reject
116
- if (action.type === "approve" || action.type === "reject") {
117
- const fresh = verifyFresh(action, pendingSnapshot);
118
- if (!fresh.ok) {
119
- results.push({ action, status: "stale_skipped", detail: fresh.reason });
120
- try {
121
- deps.metrics?.incr("tui_actions_invoked", 1);
122
- }
123
- catch { }
124
- continue;
125
- }
126
- }
127
- try {
128
- if (action.type === "approve") {
129
- deps.approve(action.proposalId);
130
- results.push({ action, status: "applied" });
131
- }
132
- else if (action.type === "reject") {
133
- deps.reject(action.proposalId, action.note);
134
- results.push({ action, status: "rejected" });
135
- }
136
- else if (action.type === "acknowledge") {
137
- deps.acknowledge(action.conflictId);
138
- results.push({ action, status: "applied" });
139
- }
140
- }
141
- catch (err) {
142
- const msg = err instanceof Error ? err.message : String(err);
143
- results.push({ action, status: "error", detail: msg });
144
- }
145
- try {
146
- deps.metrics?.incr("tui_actions_invoked", 1);
147
- }
148
- catch { }
149
- }
150
- return results;
151
- }
152
- export function writeResults(root, results) {
153
- const dir = join(root, "tui");
154
- mkdirSync(dir, { recursive: true });
155
- const payload = JSON.stringify({ generatedAt: new Date().toISOString(), results }, null, 2);
156
- const target = join(dir, "results.json");
157
- const tmp = `${target}.tmp`;
158
- writeFileSync(tmp, payload, "utf8");
159
- renameSync(tmp, target);
160
- }
161
- export function deleteMailbox(root) {
162
- try {
163
- unlinkSync(join(root, "tui", "actions.json"));
164
- }
165
- catch {
166
- // missing is fine
167
- }
168
- }
169
- /**
170
- * Convenience: read → process → write results → delete queue.
171
- * Returns results (empty if no actions). Mirrors idle-chain usage.
172
- */
173
- export function consumeMailbox(root, deps) {
174
- const { actions } = readMailbox(root);
175
- if (actions.length === 0)
176
- return [];
177
- const results = processActions(actions, deps);
178
- writeResults(root, results);
179
- deleteMailbox(root);
180
- return results;
181
- }
@@ -1,24 +0,0 @@
1
- import type { Metrics } from "./metrics.js";
2
- import type { ConflictView, HealthView, ProposalView } from "./tui-types.js";
3
- export interface FlushInput {
4
- readonly root: string;
5
- readonly proposals: readonly ProposalView[];
6
- readonly conflicts: readonly ConflictView[];
7
- readonly health: HealthView;
8
- readonly metrics?: Metrics | null;
9
- readonly version?: string;
10
- }
11
- export interface FlushResult {
12
- readonly written: string[];
13
- readonly skipped: string[];
14
- }
15
- export declare function flushSnapshots(input: FlushInput): FlushResult;
16
- /**
17
- * Tolerant JSON reader used by tests and (duplicated) by the TUI.
18
- * Returns {data} on success, {error} on missing/corrupt.
19
- */
20
- export declare function readJsonSafe(path: string): {
21
- data: unknown;
22
- } | {
23
- error: "missing" | "corrupt";
24
- };
@@ -1,158 +0,0 @@
1
- // v1.2.0 (K12-003 / plan §4.2, D12-05) — snapshot flush (pure serialization + atomic write).
2
- import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
3
- import { join } from "node:path";
4
- const CAP_BYTES = 512 * 1024;
5
- const SNAP_FILES = [
6
- "proposals.json",
7
- "conflicts.json",
8
- "health.json",
9
- "meta.json",
10
- ];
11
- function byteLen(s) {
12
- return Buffer.byteLength(s, "utf8");
13
- }
14
- function atomicWrite(target, content) {
15
- const tmp = `${target}.tmp`;
16
- writeFileSync(tmp, content, "utf8");
17
- renameSync(tmp, target);
18
- }
19
- function truncateProposals(proposals, cap) {
20
- // Estimate JSON overhead without diffs to compute available budget.
21
- // We truncate diff fields proportionally to fit cap.
22
- const serialized = JSON.stringify(proposals);
23
- if (byteLen(serialized) <= cap)
24
- return proposals;
25
- // Compute total diff length
26
- const totalDiff = proposals.reduce((acc, p) => acc + byteLen(p.diff), 0);
27
- if (totalDiff === 0)
28
- return proposals;
29
- const overhead = byteLen(serialized) - totalDiff;
30
- const budget = Math.max(0, cap - overhead - 1024); // leave margin
31
- // Distribute budget proportionally
32
- const out = [];
33
- for (let i = 0; i < proposals.length; i++) {
34
- const p = proposals[i];
35
- const diffBytes = byteLen(p.diff);
36
- const share = Math.floor((diffBytes / totalDiff) * budget);
37
- // Ensure at least 100 bytes per entry if possible, else share
38
- const sliceBytes = Math.min(diffBytes, Math.max(share, 100));
39
- // Slice diff by bytes approximated via string length; diff is ascii mostly
40
- // so byte length ~ char length. Use char slice.
41
- const approxChars = Math.floor((sliceBytes / Math.max(diffBytes, 1)) * p.diff.length);
42
- const truncatedDiff = p.diff.slice(0, Math.max(0, approxChars));
43
- const needsTrunc = truncatedDiff.length < p.diff.length;
44
- out.push({
45
- ...p,
46
- diff: needsTrunc ? `${truncatedDiff}\n…[truncated]` : p.diff,
47
- truncated: needsTrunc ? true : p.truncated,
48
- });
49
- }
50
- // If still over cap after proportional truncation, iteratively trim more
51
- let result = out;
52
- let ser = JSON.stringify(result);
53
- let iter = 0;
54
- while (byteLen(ser) > cap && iter < 5) {
55
- result = result.map((p) => {
56
- if (!p.diff || p.diff.length < 200)
57
- return p;
58
- const half = Math.floor(p.diff.length / 2);
59
- return {
60
- ...p,
61
- diff: `${p.diff.slice(0, half)}\n…[truncated]`,
62
- truncated: true,
63
- };
64
- });
65
- ser = JSON.stringify(result);
66
- iter++;
67
- }
68
- return result;
69
- }
70
- export function flushSnapshots(input) {
71
- const { root, proposals, conflicts, health, metrics, version } = input;
72
- const dir = join(root, "tui");
73
- mkdirSync(dir, { recursive: true });
74
- const generatedAt = new Date().toISOString();
75
- const written = [];
76
- const skipped = [];
77
- // Proposals — cap with truncation
78
- let propToWrite = proposals;
79
- let propJson = JSON.stringify(propToWrite, null, 2);
80
- if (byteLen(propJson) > CAP_BYTES) {
81
- propToWrite = truncateProposals(proposals, CAP_BYTES - 1024);
82
- propJson = JSON.stringify(propToWrite, null, 2);
83
- if (byteLen(propJson) > CAP_BYTES) {
84
- // Still over after truncation: truncate further by dropping diffs entirely
85
- const minimal = propToWrite.map((p) => ({
86
- ...p,
87
- diff: `${p.diff.slice(0, 500)}\n…[truncated]`,
88
- truncated: true,
89
- }));
90
- propJson = JSON.stringify(minimal, null, 2);
91
- }
92
- }
93
- atomicWrite(join(dir, "proposals.json"), propJson);
94
- written.push("proposals.json");
95
- // Conflicts
96
- let conflictsJson = JSON.stringify(conflicts, null, 2);
97
- if (byteLen(conflictsJson) > CAP_BYTES) {
98
- // Truncate summaries if needed
99
- const truncated = conflicts.map((c) => ({
100
- ...c,
101
- a_summary: c.a_summary.slice(0, 500),
102
- b_summary: c.b_summary.slice(0, 500),
103
- }));
104
- conflictsJson = JSON.stringify(truncated, null, 2);
105
- }
106
- atomicWrite(join(dir, "conflicts.json"), conflictsJson);
107
- written.push("conflicts.json");
108
- // Health
109
- let healthJson = JSON.stringify(health, null, 2);
110
- if (byteLen(healthJson) > CAP_BYTES) {
111
- // Health should never exceed cap, but truncate counters if it does
112
- const truncatedHealth = {
113
- ...health,
114
- counters: {},
115
- };
116
- healthJson = JSON.stringify(truncatedHealth, null, 2);
117
- }
118
- atomicWrite(join(dir, "health.json"), healthJson);
119
- written.push("health.json");
120
- // Meta
121
- const meta = {
122
- generatedAt,
123
- version: version ?? "1.2.0",
124
- files: SNAP_FILES.slice(0, 3),
125
- };
126
- atomicWrite(join(dir, "meta.json"), JSON.stringify(meta, null, 2));
127
- written.push("meta.json");
128
- if (metrics) {
129
- try {
130
- metrics.incr("tui_snapshots_flushed");
131
- }
132
- catch {
133
- // best-effort
134
- }
135
- }
136
- return { written, skipped };
137
- }
138
- /**
139
- * Tolerant JSON reader used by tests and (duplicated) by the TUI.
140
- * Returns {data} on success, {error} on missing/corrupt.
141
- */
142
- export function readJsonSafe(path) {
143
- try {
144
- const raw = readFileSync(path, "utf8");
145
- try {
146
- return { data: JSON.parse(raw) };
147
- }
148
- catch {
149
- return { error: "corrupt" };
150
- }
151
- }
152
- catch (err) {
153
- const code = err?.code;
154
- if (code === "ENOENT")
155
- return { error: "missing" };
156
- return { error: "corrupt" };
157
- }
158
- }
@@ -1,11 +0,0 @@
1
- import type { Store } from "./Store.js";
2
- export declare function hasColumn(store: Store, table: string, column: string): boolean;
3
- export declare function hasIgnoredColumn(store: Store): boolean;
4
- export declare function hasCuratedColumn(store: Store): boolean;
5
- export declare function hasTruthColumns(store: Store): boolean;
6
- export declare function hasRepoIdColumn(store: Store): boolean;
7
- export declare function hasLayerColumn(store: Store): boolean;
8
- export declare function hasRecurrenceColumn(store: Store): boolean;
9
- export declare function hasArchivedColumn(store: Store): boolean;
10
- export declare function hasFeedbackColumns(store: Store): boolean;
11
- export declare function hasFeedbackTable(store: Store): boolean;
@@ -1,54 +0,0 @@
1
- const cache = new WeakMap();
2
- export function hasColumn(store, table, column) {
3
- const byStore = cache.get(store);
4
- const key = `${table}.${column}`;
5
- if (byStore?.get(key) === true)
6
- return true;
7
- try {
8
- store.prepare(`SELECT ${column} FROM ${table} LIMIT 0`).get();
9
- let m = byStore;
10
- if (!m) {
11
- m = new Map();
12
- cache.set(store, m);
13
- }
14
- m.set(key, true);
15
- return true;
16
- }
17
- catch {
18
- return false;
19
- }
20
- }
21
- // v1.1.0 (K11-011 / plan §5.5, D11-06) — named helpers delegating to the registry
22
- export function hasIgnoredColumn(store) {
23
- return hasColumn(store, "memories", "ignored");
24
- }
25
- export function hasCuratedColumn(store) {
26
- return hasColumn(store, "memories", "curated");
27
- }
28
- export function hasTruthColumns(store) {
29
- return hasColumn(store, "memories", "truth_penalty");
30
- }
31
- export function hasRepoIdColumn(store) {
32
- return hasColumn(store, "memories", "repo_id");
33
- }
34
- export function hasLayerColumn(store) {
35
- return hasColumn(store, "memories", "layer");
36
- }
37
- export function hasRecurrenceColumn(store) {
38
- return hasColumn(store, "memories", "recurrence_count");
39
- }
40
- export function hasArchivedColumn(store) {
41
- return hasColumn(store, "memories", "archived_at");
42
- }
43
- export function hasFeedbackColumns(store) {
44
- return hasColumn(store, "memories", "feedback_positive");
45
- }
46
- export function hasFeedbackTable(store) {
47
- try {
48
- store.prepare("SELECT COUNT(*) FROM memory_feedback LIMIT 0").get();
49
- return true;
50
- }
51
- catch {
52
- return false;
53
- }
54
- }
@@ -1,8 +0,0 @@
1
- export declare const CONFIDENCE_MIN = 0.05;
2
- export declare const CONFIDENCE_MAX = 0.95;
3
- export declare const CONFIDENCE_BASE = 0.5;
4
- export declare const EVIDENCE_STEP = 0.1;
5
- export declare const RECURRENCE_PENALTY = 0.15;
6
- export declare const FEEDBACK_POSITIVE_STEP = 0.05;
7
- export declare const FEEDBACK_NEGATIVE_STEP = 0.1;
8
- export declare function computeConfidence(evidenceCount: number, recurrenceCount: number, positiveFeedback?: number, negativeFeedback?: number): number;