kodingo-cli 1.2.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/cloud-adapter/cloud-persistence.js +6 -0
- package/dist/adapters/db-adapter/memory-persistence.js +52 -11
- package/dist/commands/capture.js +6 -0
- package/dist/commands/deny.js +4 -0
- package/dist/commands/doctor.js +1 -0
- package/dist/commands/scan-claude.js +1 -0
- package/dist/commands/scan-repo.js +14 -0
- package/dist/ports/cloud-event-port.js +3 -0
- package/dist/ports/db-event-port.js +9 -1
- package/package.json +2 -2
|
@@ -104,6 +104,7 @@ function mapApiRecord(r) {
|
|
|
104
104
|
},
|
|
105
105
|
createdAt: new Date(r.createdAt),
|
|
106
106
|
updatedAt: new Date(r.updatedAt),
|
|
107
|
+
evidenceKind: r.evidenceKind ?? "sourced",
|
|
107
108
|
// Adapter-layer fields — not on core MemoryRecord but present in API responses
|
|
108
109
|
externalId: r.externalId ?? null,
|
|
109
110
|
correctsId: r.correctsId ?? null,
|
|
@@ -118,6 +119,8 @@ function mapApiRecord(r) {
|
|
|
118
119
|
record.fingerprint = r.fingerprint;
|
|
119
120
|
if (r.deniedAt)
|
|
120
121
|
record.deniedAt = new Date(r.deniedAt);
|
|
122
|
+
if (r.contentAsOf)
|
|
123
|
+
record.contentAsOf = new Date(r.contentAsOf);
|
|
121
124
|
return record;
|
|
122
125
|
}
|
|
123
126
|
// ── Public API (mirrors memory-persistence.ts) ────────────────────────────────
|
|
@@ -146,6 +149,7 @@ async function saveMemory(input) {
|
|
|
146
149
|
status: input.status ?? "proposed",
|
|
147
150
|
confidence: input.confidence ?? 0.3,
|
|
148
151
|
tags: input.tags ?? [],
|
|
152
|
+
evidenceKind: input.evidenceKind,
|
|
149
153
|
};
|
|
150
154
|
if (input.title)
|
|
151
155
|
body.title = input.title;
|
|
@@ -159,6 +163,8 @@ async function saveMemory(input) {
|
|
|
159
163
|
body.correctsId = input.correctsId;
|
|
160
164
|
if (input.correctedById)
|
|
161
165
|
body.correctedById = input.correctedById;
|
|
166
|
+
if (input.contentAsOf)
|
|
167
|
+
body.contentAsOf = input.contentAsOf.toISOString();
|
|
162
168
|
// Attribution
|
|
163
169
|
try {
|
|
164
170
|
const config = (0, persistence_config_1.readConfig)();
|
|
@@ -46,6 +46,26 @@ async function initDb() {
|
|
|
46
46
|
ALTER TABLE kodingo_memory ADD COLUMN IF NOT EXISTS corrects_id uuid NULL;
|
|
47
47
|
ALTER TABLE kodingo_memory ADD COLUMN IF NOT EXISTS corrected_by_id uuid NULL;
|
|
48
48
|
|
|
49
|
+
-- Real evidence provenance (mirrors kodingo-api's schema.ts) — local-mode
|
|
50
|
+
-- writes go straight here via psql, bypassing the API's enforcement
|
|
51
|
+
-- entirely, so the same real constraint needs to exist on this table too.
|
|
52
|
+
ALTER TABLE kodingo_memory ADD COLUMN IF NOT EXISTS evidence_kind text NOT NULL DEFAULT 'sourced';
|
|
53
|
+
DO $$
|
|
54
|
+
BEGIN
|
|
55
|
+
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'kodingo_memory_evidence_kind_check') THEN
|
|
56
|
+
ALTER TABLE kodingo_memory ADD CONSTRAINT kodingo_memory_evidence_kind_check CHECK (evidence_kind IN ('sourced', 'derived'));
|
|
57
|
+
END IF;
|
|
58
|
+
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'kodingo_memory_sourced_requires_external_id') THEN
|
|
59
|
+
ALTER TABLE kodingo_memory ADD CONSTRAINT kodingo_memory_sourced_requires_external_id
|
|
60
|
+
CHECK (evidence_kind = 'derived' OR (external_id IS NOT NULL AND btrim(external_id) <> '')) NOT VALID;
|
|
61
|
+
END IF;
|
|
62
|
+
END $$;
|
|
63
|
+
|
|
64
|
+
-- Real, best-known age of the underlying decision/code, separate from
|
|
65
|
+
-- created_at (mirrors kodingo-api's schema.ts). NULL when genuinely
|
|
66
|
+
-- unknown — never defaulted to created_at.
|
|
67
|
+
ALTER TABLE kodingo_memory ADD COLUMN IF NOT EXISTS content_as_of timestamptz NULL;
|
|
68
|
+
|
|
49
69
|
-- Indexes
|
|
50
70
|
CREATE INDEX IF NOT EXISTS kodingo_memory_created_at_idx
|
|
51
71
|
ON kodingo_memory (created_at);
|
|
@@ -97,11 +117,23 @@ async function saveMemory(input) {
|
|
|
97
117
|
const id = (0, uuid_1.v4)();
|
|
98
118
|
const status = input.status ?? "proposed";
|
|
99
119
|
const confidence = clamp01(input.confidence ?? 0.3);
|
|
120
|
+
// Real mechanical enforcement (mirrors kodingo-core's
|
|
121
|
+
// validateMemoryProvenance) — local-mode writes go straight to Postgres
|
|
122
|
+
// via psql, bypassing kodingo-api's routes/memory.ts entirely, so this is
|
|
123
|
+
// the one place that enforcement can actually happen for this path. Same
|
|
124
|
+
// real, honest fallback as the API: a "sourced" capture with nothing
|
|
125
|
+
// client-supplied gets a real identifier naming the capture event itself,
|
|
126
|
+
// not a fabricated placeholder.
|
|
127
|
+
const externalId = input.externalId ??
|
|
128
|
+
(input.evidenceKind === "sourced" ? `manual:${input.repoPath ?? "unknown"}:${id}` : undefined);
|
|
129
|
+
if (input.evidenceKind === "sourced" && !externalId?.trim()) {
|
|
130
|
+
throw new Error("A 'sourced' memory must include a real externalId. If this genuinely has no single real source, use evidenceKind: 'derived' instead.");
|
|
131
|
+
}
|
|
100
132
|
const sql = `
|
|
101
133
|
WITH inserted AS (
|
|
102
134
|
INSERT INTO kodingo_memory (
|
|
103
135
|
id, type, title, content, tags, repo_path,
|
|
104
|
-
status, confidence, symbol, external_id,
|
|
136
|
+
status, confidence, symbol, external_id, evidence_kind, content_as_of,
|
|
105
137
|
corrects_id, corrected_by_id
|
|
106
138
|
)
|
|
107
139
|
VALUES (
|
|
@@ -114,13 +146,15 @@ async function saveMemory(input) {
|
|
|
114
146
|
${sqlLiteral(status)},
|
|
115
147
|
${sqlNumberLiteral(confidence)},
|
|
116
148
|
${sqlNullableLiteral(input.symbol)},
|
|
117
|
-
${sqlNullableLiteral(
|
|
149
|
+
${sqlNullableLiteral(externalId)},
|
|
150
|
+
${sqlLiteral(input.evidenceKind)},
|
|
151
|
+
${sqlNullableTimestampLiteral(input.contentAsOf)},
|
|
118
152
|
${sqlNullableUuidLiteral(input.correctsId)},
|
|
119
153
|
${sqlNullableUuidLiteral(input.correctedById)}
|
|
120
154
|
)
|
|
121
155
|
RETURNING
|
|
122
156
|
id, type, title, content, tags, repo_path,
|
|
123
|
-
status, confidence, symbol, external_id,
|
|
157
|
+
status, confidence, symbol, external_id, evidence_kind, content_as_of,
|
|
124
158
|
corrects_id, corrected_by_id,
|
|
125
159
|
created_at
|
|
126
160
|
)
|
|
@@ -137,7 +171,7 @@ async function getMemoryById(id) {
|
|
|
137
171
|
SELECT row_to_json(result) FROM (
|
|
138
172
|
SELECT
|
|
139
173
|
id, type, title, content, tags, repo_path,
|
|
140
|
-
status, confidence, symbol, external_id,
|
|
174
|
+
status, confidence, symbol, external_id, evidence_kind, content_as_of,
|
|
141
175
|
corrects_id, corrected_by_id,
|
|
142
176
|
created_at
|
|
143
177
|
FROM kodingo_memory
|
|
@@ -165,7 +199,7 @@ async function updateMemoryLifecycle(params) {
|
|
|
165
199
|
WHERE id = ${sqlLiteral(params.id)}
|
|
166
200
|
RETURNING
|
|
167
201
|
id, type, title, content, tags, repo_path,
|
|
168
|
-
status, confidence, symbol, external_id,
|
|
202
|
+
status, confidence, symbol, external_id, evidence_kind, content_as_of,
|
|
169
203
|
corrects_id, corrected_by_id,
|
|
170
204
|
created_at
|
|
171
205
|
)
|
|
@@ -218,7 +252,7 @@ async function getMemoryByExternalId(externalId, repoPath) {
|
|
|
218
252
|
SELECT row_to_json(result) FROM (
|
|
219
253
|
SELECT
|
|
220
254
|
id, type, title, content, tags, repo_path,
|
|
221
|
-
status, confidence, symbol, external_id,
|
|
255
|
+
status, confidence, symbol, external_id, evidence_kind, content_as_of,
|
|
222
256
|
corrects_id, corrected_by_id,
|
|
223
257
|
created_at
|
|
224
258
|
FROM kodingo_memory
|
|
@@ -243,7 +277,7 @@ async function getProposedOrIgnoredRecordBySymbol(params) {
|
|
|
243
277
|
SELECT row_to_json(result) FROM (
|
|
244
278
|
SELECT
|
|
245
279
|
id, type, title, content, tags, repo_path,
|
|
246
|
-
status, confidence, symbol, external_id,
|
|
280
|
+
status, confidence, symbol, external_id, evidence_kind, content_as_of,
|
|
247
281
|
corrects_id, corrected_by_id,
|
|
248
282
|
created_at
|
|
249
283
|
FROM kodingo_memory
|
|
@@ -275,7 +309,7 @@ async function updateMemoryConfidence(params) {
|
|
|
275
309
|
WHERE id = ${sqlLiteral(params.id)}
|
|
276
310
|
RETURNING
|
|
277
311
|
id, type, title, content, tags, repo_path,
|
|
278
|
-
status, confidence, symbol, external_id,
|
|
312
|
+
status, confidence, symbol, external_id, evidence_kind, content_as_of,
|
|
279
313
|
corrects_id, corrected_by_id,
|
|
280
314
|
created_at
|
|
281
315
|
)
|
|
@@ -318,7 +352,7 @@ async function queryMemory(text, limit = 10, repoPath, opts = {}) {
|
|
|
318
352
|
SELECT row_to_json(result) FROM (
|
|
319
353
|
SELECT DISTINCT ON (coalesce(symbol, id::text))
|
|
320
354
|
id, type, title, content, tags, repo_path,
|
|
321
|
-
status, confidence, symbol, external_id,
|
|
355
|
+
status, confidence, symbol, external_id, evidence_kind, content_as_of,
|
|
322
356
|
corrects_id, corrected_by_id,
|
|
323
357
|
created_at
|
|
324
358
|
FROM kodingo_memory
|
|
@@ -349,7 +383,7 @@ async function queryMemory(text, limit = 10, repoPath, opts = {}) {
|
|
|
349
383
|
SELECT row_to_json(result) FROM (
|
|
350
384
|
SELECT
|
|
351
385
|
id, type, title, content, tags, repo_path,
|
|
352
|
-
status, confidence, symbol, external_id,
|
|
386
|
+
status, confidence, symbol, external_id, evidence_kind, content_as_of,
|
|
353
387
|
corrects_id, corrected_by_id,
|
|
354
388
|
created_at,
|
|
355
389
|
CASE
|
|
@@ -413,7 +447,7 @@ async function queryMemoryBySymbol(params) {
|
|
|
413
447
|
SELECT row_to_json(result) FROM (
|
|
414
448
|
SELECT
|
|
415
449
|
id, type, title, content, tags, repo_path,
|
|
416
|
-
status, confidence, symbol, external_id,
|
|
450
|
+
status, confidence, symbol, external_id, evidence_kind, content_as_of,
|
|
417
451
|
corrects_id, corrected_by_id,
|
|
418
452
|
created_at
|
|
419
453
|
FROM kodingo_memory
|
|
@@ -444,6 +478,8 @@ function mapRow(row) {
|
|
|
444
478
|
confidence,
|
|
445
479
|
symbol: row.symbol ?? null,
|
|
446
480
|
externalId: row.external_id ?? null,
|
|
481
|
+
evidenceKind: row.evidence_kind ?? "sourced",
|
|
482
|
+
contentAsOf: row.content_as_of ? new Date(row.content_as_of) : null,
|
|
447
483
|
correctsId: row.corrects_id ?? null,
|
|
448
484
|
correctedById: row.corrected_by_id ?? null,
|
|
449
485
|
createdAt: new Date(row.created_at),
|
|
@@ -509,6 +545,11 @@ function sqlNullableUuidLiteral(value) {
|
|
|
509
545
|
return "NULL";
|
|
510
546
|
return `${sqlLiteral(value)}::uuid`;
|
|
511
547
|
}
|
|
548
|
+
function sqlNullableTimestampLiteral(value) {
|
|
549
|
+
if (!value)
|
|
550
|
+
return "NULL";
|
|
551
|
+
return `${sqlLiteral(value.toISOString())}::timestamptz`;
|
|
552
|
+
}
|
|
512
553
|
function sqlArrayLiteral(values) {
|
|
513
554
|
if (!values || values.length === 0)
|
|
514
555
|
return "NULL";
|
package/dist/commands/capture.js
CHANGED
|
@@ -17,10 +17,16 @@ async function captureCommand(options) {
|
|
|
17
17
|
}
|
|
18
18
|
const tags = parseTags(options.tags);
|
|
19
19
|
const repoPath = findRepoRoot(process.cwd());
|
|
20
|
+
// A manual capture is genuinely sourced — to the real, specific event of
|
|
21
|
+
// this human's assertion, right now. Not tied to a pre-existing external
|
|
22
|
+
// artifact the way a commit or PR is, so no externalId is set here; both
|
|
23
|
+
// backends (local-mode saveMemory, kodingo-api's routes/memory.ts)
|
|
24
|
+
// construct a real one from the capture event's own identity when absent.
|
|
20
25
|
const input = {
|
|
21
26
|
type: options.type,
|
|
22
27
|
content,
|
|
23
28
|
repoPath,
|
|
29
|
+
evidenceKind: "sourced",
|
|
24
30
|
};
|
|
25
31
|
if (options.title)
|
|
26
32
|
input.title = options.title;
|
package/dist/commands/deny.js
CHANGED
|
@@ -37,12 +37,16 @@ async function denyCommand(options) {
|
|
|
37
37
|
}
|
|
38
38
|
const userTags = parseTags(options.tags);
|
|
39
39
|
const tags = mergeTags(existing.tags, userTags);
|
|
40
|
+
// A denial's corrected replacement is a new, real human assertion — the
|
|
41
|
+
// correction event itself — same class as manual capture, not simply
|
|
42
|
+
// inherited from whatever the original record's provenance was.
|
|
40
43
|
const input = {
|
|
41
44
|
type: existing.type,
|
|
42
45
|
content: correctionContent,
|
|
43
46
|
status: "proposed",
|
|
44
47
|
confidence: 0.7,
|
|
45
48
|
correctsId: existing.id,
|
|
49
|
+
evidenceKind: "sourced",
|
|
46
50
|
};
|
|
47
51
|
if (existing.repo)
|
|
48
52
|
input.repoPath = existing.repo;
|
package/dist/commands/doctor.js
CHANGED
|
@@ -104,6 +104,7 @@ function extractMemoriesFromSession(messages, sessionId, repoPath) {
|
|
|
104
104
|
repoPath,
|
|
105
105
|
status: "proposed",
|
|
106
106
|
confidence: Math.min(0.3 + score * 0.05, 0.6),
|
|
107
|
+
evidenceKind: "sourced",
|
|
107
108
|
externalId: `claude-session:${sessionId}:${Buffer.from(para.slice(0, 40)).toString("base64").slice(0, 16)}`,
|
|
108
109
|
});
|
|
109
110
|
}
|
|
@@ -112,6 +112,8 @@ async function extractSymbols(filePath) {
|
|
|
112
112
|
code: n.body.slice(0, 2000), // cap to avoid huge payloads
|
|
113
113
|
file: filePath,
|
|
114
114
|
fingerprint: n.fingerprint,
|
|
115
|
+
startLine: n.startLine,
|
|
116
|
+
endLine: n.endLine,
|
|
115
117
|
}));
|
|
116
118
|
}
|
|
117
119
|
catch {
|
|
@@ -197,6 +199,18 @@ async function inferAndCapture(token, symbol, repo, graph, repoRoot) {
|
|
|
197
199
|
machineId: (0, persistence_config_1.readConfig)().machineId ?? undefined,
|
|
198
200
|
displayName: os.hostname(),
|
|
199
201
|
fingerprint: symbol.fingerprint ?? undefined,
|
|
202
|
+
// An LLM's own inferred description of a function during a bulk scan
|
|
203
|
+
// — Kortex's own conclusion, not traceable to one real external
|
|
204
|
+
// event, same class as kodingo-api's githubScanner.ts.
|
|
205
|
+
evidenceKind: "derived",
|
|
206
|
+
// Real line range + repo-relative file path (Problem B) — lets
|
|
207
|
+
// kodingo-api resolve this symbol's real content age via git blame
|
|
208
|
+
// asynchronously after capture (it has the GitHub App token this
|
|
209
|
+
// CLI doesn't). Without these, content_as_of stays null — never
|
|
210
|
+
// guessed from scan timing.
|
|
211
|
+
startLine: symbol.startLine ?? undefined,
|
|
212
|
+
endLine: symbol.endLine ?? undefined,
|
|
213
|
+
filePath: path_1.default.relative(repoRoot, symbol.file).split(path_1.default.sep).join("/"),
|
|
200
214
|
}),
|
|
201
215
|
});
|
|
202
216
|
return saveRes.ok;
|
|
@@ -58,6 +58,7 @@ class CloudEventPort {
|
|
|
58
58
|
content: existing.content,
|
|
59
59
|
symbol: existing.symbol ?? "",
|
|
60
60
|
tags: existing.tags ?? [],
|
|
61
|
+
evidenceKind: existing.evidenceKind,
|
|
61
62
|
status: existing.status,
|
|
62
63
|
confidence: existing.confidence,
|
|
63
64
|
evidence: {
|
|
@@ -100,8 +101,10 @@ class CloudEventPort {
|
|
|
100
101
|
repoPath: this.repoPath,
|
|
101
102
|
status: "proposed",
|
|
102
103
|
confidence: decision.confidence ?? 0.3,
|
|
104
|
+
evidenceKind: "sourced",
|
|
103
105
|
externalId,
|
|
104
106
|
tags: ["signal:git", "kind:decision"],
|
|
107
|
+
contentAsOf: decision.proposedAt,
|
|
105
108
|
};
|
|
106
109
|
if (title)
|
|
107
110
|
input.title = title;
|
|
@@ -52,6 +52,7 @@ class DbEventPort {
|
|
|
52
52
|
content: existing.content,
|
|
53
53
|
symbol: existing.symbol ?? "",
|
|
54
54
|
tags: existing.tags ?? [],
|
|
55
|
+
evidenceKind: existing.evidenceKind,
|
|
55
56
|
status: existing.status,
|
|
56
57
|
confidence: existing.confidence,
|
|
57
58
|
evidence: {
|
|
@@ -74,14 +75,21 @@ class DbEventPort {
|
|
|
74
75
|
return;
|
|
75
76
|
}
|
|
76
77
|
}
|
|
77
|
-
// No existing proposed/ignored record — insert new record as usual
|
|
78
|
+
// No existing proposed/ignored record — insert new record as usual.
|
|
79
|
+
// Git-signal-derived captures are genuinely sourced — externalId here is
|
|
80
|
+
// a real git:<commitHash> reference (see the caller). contentAsOf is the
|
|
81
|
+
// real commit's own author date (decision.proposedAt) — trivially in
|
|
82
|
+
// hand here, unlike bulk/autonomous scans which have no equivalent
|
|
83
|
+
// single real event to anchor to (see contentAge.ts, kodingo-api).
|
|
78
84
|
const input = {
|
|
79
85
|
type: "decision",
|
|
80
86
|
content,
|
|
81
87
|
repoPath: this.repoPath,
|
|
82
88
|
status: "proposed",
|
|
83
89
|
confidence: 0.3,
|
|
90
|
+
evidenceKind: "sourced",
|
|
84
91
|
externalId,
|
|
92
|
+
contentAsOf: decision.proposedAt,
|
|
85
93
|
};
|
|
86
94
|
if (title)
|
|
87
95
|
input.title = title;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kodingo-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"description": "Kodingo CLI",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"private": false,
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"@typescript-eslint/typescript-estree": "^8.61.1",
|
|
27
27
|
"commander": "^12.1.0",
|
|
28
28
|
"kodingo-agent": "^0.2.0",
|
|
29
|
-
"kodingo-core": "^0.
|
|
29
|
+
"kodingo-core": "^0.3.1",
|
|
30
30
|
"open": "^11.0.0",
|
|
31
31
|
"simple-git": "^3.30.0",
|
|
32
32
|
"tree-sitter-wasms": "^0.1.13",
|