@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.
- package/README.md +84 -22
- package/dist/plugin/index.d.ts +5 -4
- package/dist/plugin/index.js +127 -113
- package/dist/plugin/native.d.ts +2 -2
- package/dist/plugin/native.js +2 -2
- package/package.json +56 -68
- package/dist/migrations/001_initial.sql +0 -92
- package/dist/migrations/002_indexes.sql +0 -14
- package/dist/migrations/003_v02_signal.sql +0 -58
- package/dist/migrations/004_v03_knowledge.sql +0 -138
- package/dist/migrations/005_v04_signal.sql +0 -57
- package/dist/migrations/006_v05_glassbox.sql +0 -118
- package/dist/migrations/007_v06_pull.sql +0 -145
- package/dist/migrations/008_v07_truth.sql +0 -124
- package/dist/migrations/009_v08_team.sql +0 -100
- package/dist/migrations/010_v09_native.sql +0 -78
- package/dist/migrations/011_v10_proven.sql +0 -69
- package/dist/migrations/012_v11_drift.sql +0 -24
- package/dist/plugin/Archiver.d.ts +0 -42
- package/dist/plugin/Archiver.js +0 -83
- package/dist/plugin/ArtifactWriter.d.ts +0 -93
- package/dist/plugin/ArtifactWriter.js +0 -291
- package/dist/plugin/CausalChain.d.ts +0 -22
- package/dist/plugin/CausalChain.js +0 -197
- package/dist/plugin/ChatBridge.d.ts +0 -41
- package/dist/plugin/ChatBridge.js +0 -103
- package/dist/plugin/ConflictDetector.d.ts +0 -35
- package/dist/plugin/ConflictDetector.js +0 -260
- package/dist/plugin/ContextInjector.d.ts +0 -222
- package/dist/plugin/ContextInjector.js +0 -533
- package/dist/plugin/ConventionMiner.d.ts +0 -35
- package/dist/plugin/ConventionMiner.js +0 -242
- package/dist/plugin/Curator.d.ts +0 -116
- package/dist/plugin/Curator.js +0 -324
- package/dist/plugin/DashboardHtml.d.ts +0 -5
- package/dist/plugin/DashboardHtml.js +0 -180
- package/dist/plugin/Feedback.d.ts +0 -67
- package/dist/plugin/Feedback.js +0 -120
- package/dist/plugin/HookLiveness.d.ts +0 -88
- package/dist/plugin/HookLiveness.js +0 -322
- package/dist/plugin/InjectionLedger.d.ts +0 -98
- package/dist/plugin/InjectionLedger.js +0 -295
- package/dist/plugin/LessonFixer.d.ts +0 -44
- package/dist/plugin/LessonFixer.js +0 -45
- package/dist/plugin/Materializer.d.ts +0 -83
- package/dist/plugin/Materializer.js +0 -181
- package/dist/plugin/MemoryService.d.ts +0 -360
- package/dist/plugin/MemoryService.js +0 -1242
- package/dist/plugin/Migrate.d.ts +0 -17
- package/dist/plugin/Migrate.js +0 -271
- package/dist/plugin/PatternMiner.d.ts +0 -49
- package/dist/plugin/PatternMiner.js +0 -132
- package/dist/plugin/QualityGate.d.ts +0 -124
- package/dist/plugin/QualityGate.js +0 -119
- package/dist/plugin/Reflector.d.ts +0 -91
- package/dist/plugin/Reflector.js +0 -307
- package/dist/plugin/RepoIdentity.d.ts +0 -124
- package/dist/plugin/RepoIdentity.js +0 -300
- package/dist/plugin/RepoTruth.d.ts +0 -80
- package/dist/plugin/RepoTruth.js +0 -599
- package/dist/plugin/Retrospective.d.ts +0 -17
- package/dist/plugin/Retrospective.js +0 -216
- package/dist/plugin/SharedLayer.d.ts +0 -159
- package/dist/plugin/SharedLayer.js +0 -462
- package/dist/plugin/Store.d.ts +0 -29
- package/dist/plugin/Store.js +0 -50
- package/dist/plugin/ToolCallObserver.d.ts +0 -32
- package/dist/plugin/ToolCallObserver.js +0 -201
- package/dist/plugin/TuiActions.d.ts +0 -43
- package/dist/plugin/TuiActions.js +0 -181
- package/dist/plugin/TuiSnapshots.d.ts +0 -24
- package/dist/plugin/TuiSnapshots.js +0 -158
- package/dist/plugin/columns.d.ts +0 -11
- package/dist/plugin/columns.js +0 -54
- package/dist/plugin/confidence.d.ts +0 -8
- package/dist/plugin/confidence.js +0 -34
- package/dist/plugin/contract.d.ts +0 -54
- package/dist/plugin/contract.js +0 -386
- package/dist/plugin/diff.d.ts +0 -8
- package/dist/plugin/diff.js +0 -182
- package/dist/plugin/escape.d.ts +0 -39
- package/dist/plugin/escape.js +0 -60
- package/dist/plugin/fingerprint.d.ts +0 -27
- package/dist/plugin/fingerprint.js +0 -73
- package/dist/plugin/inferability.d.ts +0 -32
- package/dist/plugin/inferability.js +0 -88
- package/dist/plugin/kevin_approve.d.ts +0 -34
- package/dist/plugin/kevin_approve.js +0 -52
- package/dist/plugin/kevin_audit.d.ts +0 -229
- package/dist/plugin/kevin_audit.js +0 -631
- package/dist/plugin/kevin_bench.d.ts +0 -10
- package/dist/plugin/kevin_bench.js +0 -82
- package/dist/plugin/kevin_conflicts.d.ts +0 -9
- package/dist/plugin/kevin_conflicts.js +0 -50
- package/dist/plugin/kevin_contract.d.ts +0 -8
- package/dist/plugin/kevin_contract.js +0 -59
- package/dist/plugin/kevin_doctor.d.ts +0 -62
- package/dist/plugin/kevin_doctor.js +0 -196
- package/dist/plugin/kevin_facts.d.ts +0 -42
- package/dist/plugin/kevin_facts.js +0 -36
- package/dist/plugin/kevin_forget.d.ts +0 -33
- package/dist/plugin/kevin_forget.js +0 -260
- package/dist/plugin/kevin_native.d.ts +0 -29
- package/dist/plugin/kevin_native.js +0 -79
- package/dist/plugin/kevin_propose.d.ts +0 -23
- package/dist/plugin/kevin_propose.js +0 -14
- package/dist/plugin/kevin_publish.d.ts +0 -38
- package/dist/plugin/kevin_publish.js +0 -18
- package/dist/plugin/kevin_why.d.ts +0 -23
- package/dist/plugin/kevin_why.js +0 -111
- package/dist/plugin/memory-format.d.ts +0 -20
- package/dist/plugin/memory-format.js +0 -59
- package/dist/plugin/metrics.d.ts +0 -97
- package/dist/plugin/metrics.js +0 -288
- package/dist/plugin/okf-export.d.ts +0 -3
- package/dist/plugin/okf-export.js +0 -134
- package/dist/plugin/okf-import.d.ts +0 -76
- package/dist/plugin/okf-import.js +0 -271
- package/dist/plugin/okf.d.ts +0 -107
- package/dist/plugin/okf.js +0 -303
- package/dist/plugin/perf.d.ts +0 -35
- package/dist/plugin/perf.js +0 -174
- package/dist/plugin/query-tokenizer.d.ts +0 -13
- package/dist/plugin/query-tokenizer.js +0 -133
- package/dist/plugin/redact.d.ts +0 -2
- package/dist/plugin/redact.js +0 -18
- package/dist/plugin/replay-types.d.ts +0 -69
- package/dist/plugin/replay-types.js +0 -156
- package/dist/plugin/replay.d.ts +0 -36
- package/dist/plugin/replay.js +0 -202
- package/dist/plugin/sqlite-adapter.d.ts +0 -12
- package/dist/plugin/sqlite-adapter.js +0 -81
- package/dist/plugin/time-ms.d.ts +0 -1
- package/dist/plugin/time-ms.js +0 -16
- package/dist/plugin/tui-types.d.ts +0 -59
- package/dist/plugin/tui-types.js +0 -4
- package/dist/plugin/tui.d.ts +0 -18
- package/dist/plugin/tui.js +0 -198
- package/dist/plugin/uuid.d.ts +0 -1
- package/dist/plugin/uuid.js +0 -50
|
@@ -1,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
|
-
}
|
package/dist/plugin/columns.d.ts
DELETED
|
@@ -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;
|
package/dist/plugin/columns.js
DELETED
|
@@ -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;
|