kodingo-cli 1.3.4 → 1.3.5

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.
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ /**
3
+ * Real tests for adapters/ai/inference.ts's auth fix — build brief "Item 8
4
+ * — the temporal lobe", 2026-10-01.
5
+ *
6
+ * FINDING this closes: inferDecisionSummary (the git-commit-triggered
7
+ * capture surface, via cloud-event-port.ts/db-event-port.ts) was calling
8
+ * POST /infer with no auth header at all — every real call would 401 and
9
+ * silently fall back to the non-LLM template text, meaning this surface
10
+ * never actually reached the shared, semantically-reasoning-enriched
11
+ * prompt built into routes/infer.ts (kodingo-api). These tests prove the
12
+ * real fix: a real token now reaches the request as X-Kodingo-Token, and
13
+ * the existing, honest fallback behavior is completely unchanged when no
14
+ * token is available.
15
+ */
16
+ var __importDefault = (this && this.__importDefault) || function (mod) {
17
+ return (mod && mod.__esModule) ? mod : { "default": mod };
18
+ };
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ const node_test_1 = require("node:test");
21
+ const strict_1 = __importDefault(require("node:assert/strict"));
22
+ const inference_1 = require("../adapters/ai/inference");
23
+ (0, node_test_1.describe)("inferDecisionSummary — real auth fix", () => {
24
+ const originalFetch = globalThis.fetch;
25
+ (0, node_test_1.afterEach)(() => {
26
+ globalThis.fetch = originalFetch;
27
+ });
28
+ (0, node_test_1.it)("sends the real X-Kodingo-Token header when a real token is available", async () => {
29
+ let capturedHeaders;
30
+ let capturedUrl;
31
+ globalThis.fetch = (async (url, init) => {
32
+ capturedUrl = String(url);
33
+ capturedHeaders = init.headers;
34
+ return new Response(JSON.stringify({ title: "t", content: "A real, LLM-authored decision summary.", type: "decision", tags: [] }), { status: 200 });
35
+ });
36
+ const result = await (0, inference_1.inferDecisionSummary)({
37
+ diff: "commit abc1234\n+ real change",
38
+ changedFiles: ["src/real.ts"],
39
+ symbol: "realFunction",
40
+ commitMessage: "real commit",
41
+ repoName: "real-repo",
42
+ token: "real-project-token",
43
+ apiUrl: "https://example.invalid",
44
+ });
45
+ strict_1.default.equal(result, "A real, LLM-authored decision summary.");
46
+ strict_1.default.equal(capturedUrl, "https://example.invalid/infer");
47
+ strict_1.default.equal(capturedHeaders?.["X-Kodingo-Token"], "real-project-token");
48
+ });
49
+ (0, node_test_1.it)("falls back to the honest template — never calls the network at all — when no real token is available (the pre-existing, unchanged behavior)", async () => {
50
+ let fetchCalled = false;
51
+ globalThis.fetch = (async () => {
52
+ fetchCalled = true;
53
+ throw new Error("should never be called without a real token");
54
+ });
55
+ const result = await (0, inference_1.inferDecisionSummary)({
56
+ diff: "commit abc1234ff\n+ real change",
57
+ changedFiles: ["src/a.ts", "src/b.ts"],
58
+ symbol: "realFunction",
59
+ commitMessage: "a real commit message",
60
+ repoName: "real-repo",
61
+ });
62
+ strict_1.default.equal(fetchCalled, false);
63
+ strict_1.default.match(result, /Changes to realFunction across 2 file\(s\) in commit abc1234f/);
64
+ strict_1.default.match(result, /a real commit message/);
65
+ });
66
+ (0, node_test_1.it)("falls back honestly when the real request itself fails (e.g. a genuine 401)", async () => {
67
+ globalThis.fetch = (async () => new Response("", { status: 401 }));
68
+ const result = await (0, inference_1.inferDecisionSummary)({
69
+ diff: "commit def5678\n+ change",
70
+ changedFiles: ["src/x.ts"],
71
+ symbol: "x",
72
+ commitMessage: "msg",
73
+ repoName: "real-repo",
74
+ token: "a-token-the-server-happens-to-reject",
75
+ });
76
+ strict_1.default.match(result, /Changes to x across 1 file\(s\)/);
77
+ });
78
+ });
@@ -6,9 +6,12 @@ const KODINGO_API_URL = process.env.KODINGO_API_URL ?? "https://kodingo-api.onre
6
6
  async function inferDecisionSummary(input) {
7
7
  try {
8
8
  const code = input.diff.slice(0, 2000);
9
- const res = await fetch(`${KODINGO_API_URL}/infer`, {
9
+ if (!input.token)
10
+ return buildFallback(input);
11
+ const apiUrl = input.apiUrl ?? KODINGO_API_URL;
12
+ const res = await fetch(`${apiUrl}/infer`, {
10
13
  method: "POST",
11
- headers: { "Content-Type": "application/json" },
14
+ headers: { "Content-Type": "application/json", "X-Kodingo-Token": input.token },
12
15
  body: JSON.stringify({ symbol: input.symbol, code }),
13
16
  });
14
17
  if (!res.ok) {
@@ -15,23 +15,8 @@ const db_event_port_1 = require("../ports/db-event-port");
15
15
  const cloud_event_port_1 = require("../ports/cloud-event-port");
16
16
  const persistence_config_2 = require("../utils/persistence-config");
17
17
  const repo_identity_1 = require("../utils/repo-identity");
18
- const API_BASE_DEFAULT = "https://kodingo-api.onrender.com";
19
- function getCredentials(repoRoot) {
20
- const config = (0, persistence_config_1.readConfig)();
21
- const normRoot = repoRoot.replace(/\/+$/, "");
22
- let wsEntry = config.workspaces?.[normRoot] ?? config.workspaces?.[normRoot + "/"];
23
- // Walk parent directories
24
- let current = normRoot;
25
- while (!wsEntry && current !== path_1.default.dirname(current)) {
26
- current = path_1.default.dirname(current);
27
- wsEntry = config.workspaces?.[current] ?? config.workspaces?.[current + "/"];
28
- }
29
- const token = wsEntry?.token ?? "";
30
- const apiUrl = wsEntry?.apiUrl ?? config.apiUrl ?? API_BASE_DEFAULT;
31
- return { token, apiUrl };
32
- }
33
18
  async function detectContextDecay(repoRoot, changedFiles, repoName) {
34
- const { token, apiUrl } = getCredentials(repoRoot);
19
+ const { token, apiUrl } = (0, persistence_config_1.getCredentials)(repoRoot);
35
20
  if (!token)
36
21
  return;
37
22
  for (const filePath of changedFiles) {
@@ -106,7 +91,7 @@ async function scanGitCommand(options) {
106
91
  console.log(`Git scan complete (latest commit) for repo: ${repoRoot} commit: ${commitHash.slice(0, 8)}`);
107
92
  // Send code_change signals for functions touched in this commit that already have memory
108
93
  try {
109
- const { token, apiUrl } = getCredentials(repoRoot);
94
+ const { token, apiUrl } = (0, persistence_config_1.getCredentials)(repoRoot);
110
95
  const diffOutput = await git.diff([`${commitHash}^`, commitHash, '--name-only']).catch(() => '');
111
96
  const commitChangedFiles = diffOutput.split('\n').filter(Boolean).map((f) => path_1.default.join(repoRoot, f)).filter((f) => /\.(ts|tsx|js|jsx|py|go|rs|php|java|kt|rb)$/.test(f));
112
97
  if (token && commitChangedFiles.length > 0) {
@@ -7,6 +7,7 @@ exports.CloudEventPort = void 0;
7
7
  const kodingo_core_1 = require("kodingo-core");
8
8
  const persistence_config_1 = require("../utils/persistence-config");
9
9
  const inference_1 = require("../adapters/ai/inference");
10
+ const persistence_config_2 = require("../utils/persistence-config");
10
11
  const path_1 = __importDefault(require("path"));
11
12
  /**
12
13
  * EventPort implementation that persists via getPersistence().
@@ -152,6 +153,7 @@ class CloudEventPort {
152
153
  const filePaths = extractFilePaths(filesChanged);
153
154
  const repoName = path_1.default.basename(this.repoPath);
154
155
  const shortHash = String(commitHash).slice(0, 8);
156
+ const { token, apiUrl } = (0, persistence_config_2.getCredentials)(this.repoPath);
155
157
  const content = await (0, inference_1.inferDecisionSummary)({
156
158
  diff: typeof summary?.rawDiff === "string"
157
159
  ? summary.rawDiff
@@ -160,6 +162,8 @@ class CloudEventPort {
160
162
  symbol: symbol ?? "unknown",
161
163
  commitMessage: decision.rationale ?? "",
162
164
  repoName,
165
+ token,
166
+ apiUrl,
163
167
  });
164
168
  const title = symbol
165
169
  ? `Decision: changes to ${symbol}`
@@ -7,6 +7,7 @@ exports.DbEventPort = void 0;
7
7
  const kodingo_core_1 = require("kodingo-core");
8
8
  const memory_persistence_1 = require("../adapters/db-adapter/memory-persistence");
9
9
  const inference_1 = require("../adapters/ai/inference");
10
+ const persistence_config_1 = require("../utils/persistence-config");
10
11
  const path_1 = __importDefault(require("path"));
11
12
  /**
12
13
  * Minimal EventPort implementation for the CLI.
@@ -113,6 +114,7 @@ class DbEventPort {
113
114
  const filePaths = extractFilePaths(filesChanged);
114
115
  const repoName = path_1.default.basename(this.repoPath);
115
116
  const shortHash = String(commitHash).slice(0, 8);
117
+ const { token, apiUrl } = (0, persistence_config_1.getCredentials)(this.repoPath);
116
118
  const content = await (0, inference_1.inferDecisionSummary)({
117
119
  diff: typeof summary?.rawDiff === "string"
118
120
  ? summary.rawDiff
@@ -121,6 +123,8 @@ class DbEventPort {
121
123
  symbol: symbol ?? "unknown",
122
124
  commitMessage: decision.rationale ?? "",
123
125
  repoName,
126
+ token,
127
+ apiUrl,
124
128
  });
125
129
  const title = symbol
126
130
  ? `Decision: changes to ${symbol}`
@@ -9,6 +9,7 @@ exports.writeConfig = writeConfig;
9
9
  exports.writeWorkspaceConfig = writeWorkspaceConfig;
10
10
  exports.readWorkspaceConfig = readWorkspaceConfig;
11
11
  exports.isCloudMode = isCloudMode;
12
+ exports.getCredentials = getCredentials;
12
13
  exports.getPersistence = getPersistence;
13
14
  /**
14
15
  * Persistence config switcher.
@@ -66,6 +67,32 @@ function readWorkspaceConfig(repoPath) {
66
67
  function isCloudMode() {
67
68
  return readConfig().mode === "cloud";
68
69
  }
70
+ const API_BASE_DEFAULT = "https://kodingo-api.onrender.com";
71
+ /**
72
+ * Real, workspace-scoped project credentials, walking up to parent
73
+ * directories when the exact repoRoot isn't a registered workspace key.
74
+ * Moved here from scan-git.ts (build brief "Item 8 — the temporal lobe",
75
+ * 2026-10-01) — it originally lived there as a private helper, but
76
+ * cloud-event-port.ts/db-event-port.ts now need this SAME real lookup too
77
+ * (see those files' own doc comments — inferDecisionSummary was calling
78
+ * POST /infer with no auth header at all). Importing it back from
79
+ * scan-git.ts would create a real circular dependency (scan-git.ts already
80
+ * imports both event-port classes); this lower-level module has no such
81
+ * cycle. Zero behavior change from the original — a pure relocation.
82
+ */
83
+ function getCredentials(repoRoot) {
84
+ const config = readConfig();
85
+ const normRoot = repoRoot.replace(/\/+$/, "");
86
+ let wsEntry = config.workspaces?.[normRoot] ?? config.workspaces?.[normRoot + "/"];
87
+ let current = normRoot;
88
+ while (!wsEntry && current !== node_path_1.default.dirname(current)) {
89
+ current = node_path_1.default.dirname(current);
90
+ wsEntry = config.workspaces?.[current] ?? config.workspaces?.[current + "/"];
91
+ }
92
+ const token = wsEntry?.token ?? "";
93
+ const apiUrl = wsEntry?.apiUrl ?? config.apiUrl ?? API_BASE_DEFAULT;
94
+ return { token, apiUrl };
95
+ }
69
96
  /**
70
97
  * Returns the correct persistence adapter based on config.
71
98
  * Import this in commands instead of importing the adapters directly.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kodingo-cli",
3
- "version": "1.3.4",
3
+ "version": "1.3.5",
4
4
  "description": "Kodingo CLI",
5
5
  "license": "MIT",
6
6
  "private": false,