kodingo-cli 1.2.0 → 1.3.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.
@@ -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,
@@ -146,6 +147,7 @@ async function saveMemory(input) {
146
147
  status: input.status ?? "proposed",
147
148
  confidence: input.confidence ?? 0.3,
148
149
  tags: input.tags ?? [],
150
+ evidenceKind: input.evidenceKind,
149
151
  };
150
152
  if (input.title)
151
153
  body.title = input.title;
@@ -46,6 +46,21 @@ 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
+
49
64
  -- Indexes
50
65
  CREATE INDEX IF NOT EXISTS kodingo_memory_created_at_idx
51
66
  ON kodingo_memory (created_at);
@@ -97,11 +112,23 @@ async function saveMemory(input) {
97
112
  const id = (0, uuid_1.v4)();
98
113
  const status = input.status ?? "proposed";
99
114
  const confidence = clamp01(input.confidence ?? 0.3);
115
+ // Real mechanical enforcement (mirrors kodingo-core's
116
+ // validateMemoryProvenance) — local-mode writes go straight to Postgres
117
+ // via psql, bypassing kodingo-api's routes/memory.ts entirely, so this is
118
+ // the one place that enforcement can actually happen for this path. Same
119
+ // real, honest fallback as the API: a "sourced" capture with nothing
120
+ // client-supplied gets a real identifier naming the capture event itself,
121
+ // not a fabricated placeholder.
122
+ const externalId = input.externalId ??
123
+ (input.evidenceKind === "sourced" ? `manual:${input.repoPath ?? "unknown"}:${id}` : undefined);
124
+ if (input.evidenceKind === "sourced" && !externalId?.trim()) {
125
+ throw new Error("A 'sourced' memory must include a real externalId. If this genuinely has no single real source, use evidenceKind: 'derived' instead.");
126
+ }
100
127
  const sql = `
101
128
  WITH inserted AS (
102
129
  INSERT INTO kodingo_memory (
103
130
  id, type, title, content, tags, repo_path,
104
- status, confidence, symbol, external_id,
131
+ status, confidence, symbol, external_id, evidence_kind,
105
132
  corrects_id, corrected_by_id
106
133
  )
107
134
  VALUES (
@@ -114,13 +141,14 @@ async function saveMemory(input) {
114
141
  ${sqlLiteral(status)},
115
142
  ${sqlNumberLiteral(confidence)},
116
143
  ${sqlNullableLiteral(input.symbol)},
117
- ${sqlNullableLiteral(input.externalId)},
144
+ ${sqlNullableLiteral(externalId)},
145
+ ${sqlLiteral(input.evidenceKind)},
118
146
  ${sqlNullableUuidLiteral(input.correctsId)},
119
147
  ${sqlNullableUuidLiteral(input.correctedById)}
120
148
  )
121
149
  RETURNING
122
150
  id, type, title, content, tags, repo_path,
123
- status, confidence, symbol, external_id,
151
+ status, confidence, symbol, external_id, evidence_kind,
124
152
  corrects_id, corrected_by_id,
125
153
  created_at
126
154
  )
@@ -137,7 +165,7 @@ async function getMemoryById(id) {
137
165
  SELECT row_to_json(result) FROM (
138
166
  SELECT
139
167
  id, type, title, content, tags, repo_path,
140
- status, confidence, symbol, external_id,
168
+ status, confidence, symbol, external_id, evidence_kind,
141
169
  corrects_id, corrected_by_id,
142
170
  created_at
143
171
  FROM kodingo_memory
@@ -165,7 +193,7 @@ async function updateMemoryLifecycle(params) {
165
193
  WHERE id = ${sqlLiteral(params.id)}
166
194
  RETURNING
167
195
  id, type, title, content, tags, repo_path,
168
- status, confidence, symbol, external_id,
196
+ status, confidence, symbol, external_id, evidence_kind,
169
197
  corrects_id, corrected_by_id,
170
198
  created_at
171
199
  )
@@ -218,7 +246,7 @@ async function getMemoryByExternalId(externalId, repoPath) {
218
246
  SELECT row_to_json(result) FROM (
219
247
  SELECT
220
248
  id, type, title, content, tags, repo_path,
221
- status, confidence, symbol, external_id,
249
+ status, confidence, symbol, external_id, evidence_kind,
222
250
  corrects_id, corrected_by_id,
223
251
  created_at
224
252
  FROM kodingo_memory
@@ -243,7 +271,7 @@ async function getProposedOrIgnoredRecordBySymbol(params) {
243
271
  SELECT row_to_json(result) FROM (
244
272
  SELECT
245
273
  id, type, title, content, tags, repo_path,
246
- status, confidence, symbol, external_id,
274
+ status, confidence, symbol, external_id, evidence_kind,
247
275
  corrects_id, corrected_by_id,
248
276
  created_at
249
277
  FROM kodingo_memory
@@ -275,7 +303,7 @@ async function updateMemoryConfidence(params) {
275
303
  WHERE id = ${sqlLiteral(params.id)}
276
304
  RETURNING
277
305
  id, type, title, content, tags, repo_path,
278
- status, confidence, symbol, external_id,
306
+ status, confidence, symbol, external_id, evidence_kind,
279
307
  corrects_id, corrected_by_id,
280
308
  created_at
281
309
  )
@@ -318,7 +346,7 @@ async function queryMemory(text, limit = 10, repoPath, opts = {}) {
318
346
  SELECT row_to_json(result) FROM (
319
347
  SELECT DISTINCT ON (coalesce(symbol, id::text))
320
348
  id, type, title, content, tags, repo_path,
321
- status, confidence, symbol, external_id,
349
+ status, confidence, symbol, external_id, evidence_kind,
322
350
  corrects_id, corrected_by_id,
323
351
  created_at
324
352
  FROM kodingo_memory
@@ -349,7 +377,7 @@ async function queryMemory(text, limit = 10, repoPath, opts = {}) {
349
377
  SELECT row_to_json(result) FROM (
350
378
  SELECT
351
379
  id, type, title, content, tags, repo_path,
352
- status, confidence, symbol, external_id,
380
+ status, confidence, symbol, external_id, evidence_kind,
353
381
  corrects_id, corrected_by_id,
354
382
  created_at,
355
383
  CASE
@@ -413,7 +441,7 @@ async function queryMemoryBySymbol(params) {
413
441
  SELECT row_to_json(result) FROM (
414
442
  SELECT
415
443
  id, type, title, content, tags, repo_path,
416
- status, confidence, symbol, external_id,
444
+ status, confidence, symbol, external_id, evidence_kind,
417
445
  corrects_id, corrected_by_id,
418
446
  created_at
419
447
  FROM kodingo_memory
@@ -444,6 +472,7 @@ function mapRow(row) {
444
472
  confidence,
445
473
  symbol: row.symbol ?? null,
446
474
  externalId: row.external_id ?? null,
475
+ evidenceKind: row.evidence_kind ?? "sourced",
447
476
  correctsId: row.corrects_id ?? null,
448
477
  correctedById: row.corrected_by_id ?? null,
449
478
  createdAt: new Date(row.created_at),
@@ -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;
@@ -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;
@@ -58,6 +58,7 @@ async function runDoctor(results) {
58
58
  content: "doctor saved",
59
59
  tags: ["smoke", "doctor"],
60
60
  repoPath: process.cwd(),
61
+ evidenceKind: "derived", // a throwaway diagnostic row, not a real capture
61
62
  });
62
63
  tempId = inserted.id;
63
64
  }, results);
@@ -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
  }
@@ -197,6 +197,10 @@ async function inferAndCapture(token, symbol, repo, graph, repoRoot) {
197
197
  machineId: (0, persistence_config_1.readConfig)().machineId ?? undefined,
198
198
  displayName: os.hostname(),
199
199
  fingerprint: symbol.fingerprint ?? undefined,
200
+ // An LLM's own inferred description of a function during a bulk scan
201
+ // — Kortex's own conclusion, not traceable to one real external
202
+ // event, same class as kodingo-api's githubScanner.ts.
203
+ evidenceKind: "derived",
200
204
  }),
201
205
  });
202
206
  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,6 +101,7 @@ 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"],
105
107
  };
@@ -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,13 +75,16 @@ 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).
78
81
  const input = {
79
82
  type: "decision",
80
83
  content,
81
84
  repoPath: this.repoPath,
82
85
  status: "proposed",
83
86
  confidence: 0.3,
87
+ evidenceKind: "sourced",
84
88
  externalId,
85
89
  };
86
90
  if (title)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kodingo-cli",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
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.2.1",
29
+ "kodingo-core": "^0.3.0",
30
30
  "open": "^11.0.0",
31
31
  "simple-git": "^3.30.0",
32
32
  "tree-sitter-wasms": "^0.1.13",