kodingo-cli 1.0.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 +91 -0
- package/dist/adapters/agent-adapter/agent-connector.js +1 -0
- package/dist/adapters/ai/inference.js +55 -0
- package/dist/adapters/cloud-adapter/cloud-persistence.js +290 -0
- package/dist/adapters/db-adapter/memory-persistence.js +521 -0
- package/dist/adapters/git-adapter/git-listener.js +188 -0
- package/dist/cli.js +181 -0
- package/dist/commands/add-decision.js +1 -0
- package/dist/commands/affirm.js +41 -0
- package/dist/commands/canonicalize-symbol.js +95 -0
- package/dist/commands/capture.js +67 -0
- package/dist/commands/deny.js +67 -0
- package/dist/commands/doctor.js +168 -0
- package/dist/commands/explain-symbol.js +84 -0
- package/dist/commands/ignore.js +19 -0
- package/dist/commands/index.js +1 -0
- package/dist/commands/init.js +135 -0
- package/dist/commands/install-hook.js +61 -0
- package/dist/commands/query-memory.js +61 -0
- package/dist/commands/query-symbol.js +63 -0
- package/dist/commands/scan-git.js +59 -0
- package/dist/commands/uninstall-hook.js +51 -0
- package/dist/ports/cloud-event-port.js +207 -0
- package/dist/ports/db-event-port.js +195 -0
- package/dist/utils/persistence-config.js +69 -0
- package/dist/utils/repo-scope.js +48 -0
- package/package.json +37 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const commander_1 = require("commander");
|
|
5
|
+
const capture_1 = require("./commands/capture");
|
|
6
|
+
const doctor_1 = require("./commands/doctor");
|
|
7
|
+
const query_memory_1 = require("./commands/query-memory");
|
|
8
|
+
const query_symbol_1 = require("./commands/query-symbol");
|
|
9
|
+
const explain_symbol_1 = require("./commands/explain-symbol");
|
|
10
|
+
const canonicalize_symbol_1 = require("./commands/canonicalize-symbol");
|
|
11
|
+
const affirm_1 = require("./commands/affirm");
|
|
12
|
+
const ignore_1 = require("./commands/ignore");
|
|
13
|
+
const deny_1 = require("./commands/deny");
|
|
14
|
+
const scan_git_1 = require("./commands/scan-git");
|
|
15
|
+
const init_1 = require("./commands/init");
|
|
16
|
+
const install_hook_1 = require("./commands/install-hook");
|
|
17
|
+
const program = new commander_1.Command();
|
|
18
|
+
program.name("kodingo").description("Kodingo CLI");
|
|
19
|
+
// ── init ──────────────────────────────────────────────────────────────────────
|
|
20
|
+
(0, init_1.registerInitCommand)(program);
|
|
21
|
+
// ── capture ───────────────────────────────────────────────────────────────────
|
|
22
|
+
program
|
|
23
|
+
.command("capture")
|
|
24
|
+
.description("Capture a memory record and persist it")
|
|
25
|
+
.option("--type <type>", "decision | note | context", "note")
|
|
26
|
+
.option("--title <title>", "optional title")
|
|
27
|
+
.option("--tags <tags>", "comma-separated tags")
|
|
28
|
+
.option("--content <content>", "content body")
|
|
29
|
+
.action(async (options) => {
|
|
30
|
+
await (0, capture_1.captureCommand)({
|
|
31
|
+
type: options.type,
|
|
32
|
+
title: options.title,
|
|
33
|
+
tags: options.tags,
|
|
34
|
+
content: options.content,
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
// ── query-memory ──────────────────────────────────────────────────────────────
|
|
38
|
+
program
|
|
39
|
+
.command("query-memory")
|
|
40
|
+
.description("Query stored memory records")
|
|
41
|
+
.argument("<text>", "text to search")
|
|
42
|
+
.option("--limit <number>", "max results", "10")
|
|
43
|
+
.option("--global", "search across all repos (ignore repo scope)")
|
|
44
|
+
.option("--repo <path>", "explicit repo path to scope results")
|
|
45
|
+
.option("--dedupe-by-symbol", "return only the best record per symbol")
|
|
46
|
+
.option("--include-non-canonical", "include denied records in results")
|
|
47
|
+
.action(async (text, options) => {
|
|
48
|
+
const parsed = Number.parseInt(String(options.limit), 10);
|
|
49
|
+
const limit = Number.isNaN(parsed) ? 10 : parsed;
|
|
50
|
+
const queryOptions = { limit };
|
|
51
|
+
if (options.global === true)
|
|
52
|
+
queryOptions.global = true;
|
|
53
|
+
if (options.repo)
|
|
54
|
+
queryOptions.repo = String(options.repo);
|
|
55
|
+
if (options.dedupeBySymbol === true)
|
|
56
|
+
queryOptions.dedupeBySymbol = true;
|
|
57
|
+
if (options.includeNonCanonical === true)
|
|
58
|
+
queryOptions.includeNonCanonical = true;
|
|
59
|
+
await (0, query_memory_1.queryMemoryCommand)(String(text), queryOptions);
|
|
60
|
+
});
|
|
61
|
+
// ── query-symbol ──────────────────────────────────────────────────────────────
|
|
62
|
+
program
|
|
63
|
+
.command("query-symbol")
|
|
64
|
+
.description("Query stored memory records by exact symbol")
|
|
65
|
+
.argument("<symbol>", "symbol to search")
|
|
66
|
+
.option("--limit <number>", "max results", "1")
|
|
67
|
+
.option("--global", "search across all repos (ignore repo scope)")
|
|
68
|
+
.option("--repo <path>", "explicit repo path to scope results")
|
|
69
|
+
.option("--include-denied", "include denied records")
|
|
70
|
+
.option("--only-canonical", "only return affirmed records")
|
|
71
|
+
.action(async (symbol, options) => {
|
|
72
|
+
const parsed = Number.parseInt(String(options.limit), 10);
|
|
73
|
+
const limit = Number.isNaN(parsed) ? 1 : parsed;
|
|
74
|
+
const queryOptions = {
|
|
75
|
+
limit,
|
|
76
|
+
includeDenied: options.includeDenied === true,
|
|
77
|
+
onlyCanonical: options.onlyCanonical === true,
|
|
78
|
+
};
|
|
79
|
+
if (options.global === true)
|
|
80
|
+
queryOptions.global = true;
|
|
81
|
+
if (options.repo)
|
|
82
|
+
queryOptions.repo = String(options.repo);
|
|
83
|
+
await (0, query_symbol_1.querySymbolCommand)(String(symbol), queryOptions);
|
|
84
|
+
});
|
|
85
|
+
// ── explain-symbol ────────────────────────────────────────────────────────────
|
|
86
|
+
program
|
|
87
|
+
.command("explain-symbol")
|
|
88
|
+
.description("Explain what a symbol is and why it exists (canonical answer)")
|
|
89
|
+
.argument("<symbol>", "symbol to explain")
|
|
90
|
+
.option("--global", "search across all repos (ignore repo scope)")
|
|
91
|
+
.option("--repo <path>", "explicit repo path to scope results")
|
|
92
|
+
.action(async (symbol, options) => {
|
|
93
|
+
const queryOptions = {};
|
|
94
|
+
if (options.global === true)
|
|
95
|
+
queryOptions.global = true;
|
|
96
|
+
if (options.repo)
|
|
97
|
+
queryOptions.repo = String(options.repo);
|
|
98
|
+
await (0, explain_symbol_1.explainSymbolCommand)(String(symbol), queryOptions);
|
|
99
|
+
});
|
|
100
|
+
// ── canonicalize-symbol ───────────────────────────────────────────────────────
|
|
101
|
+
program
|
|
102
|
+
.command("canonicalize-symbol")
|
|
103
|
+
.description("Suppress stale proposed siblings for a symbol, keeping affirmed record as canonical truth")
|
|
104
|
+
.argument("<symbol>", "symbol to canonicalize")
|
|
105
|
+
.option("--global", "search across all repos (ignore repo scope)")
|
|
106
|
+
.option("--repo <path>", "explicit repo path to scope results")
|
|
107
|
+
.option("--dry-run", "show what would be suppressed without making changes")
|
|
108
|
+
.action(async (symbol, options) => {
|
|
109
|
+
const queryOptions = {};
|
|
110
|
+
if (options.global === true)
|
|
111
|
+
queryOptions.global = true;
|
|
112
|
+
if (options.repo)
|
|
113
|
+
queryOptions.repo = String(options.repo);
|
|
114
|
+
if (options.dryRun === true)
|
|
115
|
+
queryOptions.dryRun = true;
|
|
116
|
+
await (0, canonicalize_symbol_1.canonicalizeSymbolCommand)(String(symbol), queryOptions);
|
|
117
|
+
});
|
|
118
|
+
// ── affirm ────────────────────────────────────────────────────────────────────
|
|
119
|
+
program
|
|
120
|
+
.command("affirm")
|
|
121
|
+
.description("Affirm a memory record (promote to canonical)")
|
|
122
|
+
.argument("<id>", "memory id")
|
|
123
|
+
.action(async (id) => {
|
|
124
|
+
await (0, affirm_1.affirmCommand)({ id: String(id) });
|
|
125
|
+
});
|
|
126
|
+
// ── ignore ────────────────────────────────────────────────────────────────────
|
|
127
|
+
program
|
|
128
|
+
.command("ignore")
|
|
129
|
+
.description("Ignore a memory record (retain as non-canonical)")
|
|
130
|
+
.argument("<id>", "memory id")
|
|
131
|
+
.action(async (id) => {
|
|
132
|
+
await (0, ignore_1.ignoreCommand)({ id: String(id) });
|
|
133
|
+
});
|
|
134
|
+
// ── deny ──────────────────────────────────────────────────────────────────────
|
|
135
|
+
program
|
|
136
|
+
.command("deny")
|
|
137
|
+
.description("Deny a memory record and create a corrected replacement proposal")
|
|
138
|
+
.argument("<id>", "memory id")
|
|
139
|
+
.option("--title <title>", "optional title for corrected record")
|
|
140
|
+
.option("--tags <tags>", "comma-separated tags for corrected record")
|
|
141
|
+
.option("--content <content>", "corrected truth content")
|
|
142
|
+
.action(async (id, options) => {
|
|
143
|
+
const payload = { id: String(id) };
|
|
144
|
+
if (options.title)
|
|
145
|
+
payload.title = String(options.title);
|
|
146
|
+
if (options.tags)
|
|
147
|
+
payload.tags = String(options.tags);
|
|
148
|
+
if (options.content)
|
|
149
|
+
payload.content = String(options.content);
|
|
150
|
+
await (0, deny_1.denyCommand)(payload);
|
|
151
|
+
});
|
|
152
|
+
// ── scan-git ──────────────────────────────────────────────────────────────────
|
|
153
|
+
program
|
|
154
|
+
.command("scan-git")
|
|
155
|
+
.description("Scan latest git commit and store an inferred proposed decision")
|
|
156
|
+
.option("--repo <path>", "repo path to scan")
|
|
157
|
+
.action(async (options) => {
|
|
158
|
+
const payload = {};
|
|
159
|
+
if (options.repo)
|
|
160
|
+
payload.repo = String(options.repo);
|
|
161
|
+
await (0, scan_git_1.scanGitCommand)(payload);
|
|
162
|
+
});
|
|
163
|
+
// ── doctor ────────────────────────────────────────────────────────────────────
|
|
164
|
+
program
|
|
165
|
+
.command("doctor")
|
|
166
|
+
.description("Run environment and database health checks")
|
|
167
|
+
.action(async () => {
|
|
168
|
+
await (0, doctor_1.doctorCommand)();
|
|
169
|
+
});
|
|
170
|
+
// ── install-hook ──────────────────────────────────────────────────────────────
|
|
171
|
+
program
|
|
172
|
+
.command("install-hook")
|
|
173
|
+
.description("Install kodingo post-commit git hook in the current repo")
|
|
174
|
+
.option("--repo <path>", "repo path to install hook into")
|
|
175
|
+
.action(async (options) => {
|
|
176
|
+
const payload = {};
|
|
177
|
+
if (options.repo)
|
|
178
|
+
payload.repo = String(options.repo);
|
|
179
|
+
await (0, install_hook_1.installHookCommand)(payload);
|
|
180
|
+
});
|
|
181
|
+
program.parse(process.argv);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.affirmCommand = affirmCommand;
|
|
4
|
+
const persistence_config_1 = require("../utils/persistence-config");
|
|
5
|
+
function clamp01(n) {
|
|
6
|
+
if (!Number.isFinite(n))
|
|
7
|
+
return 0;
|
|
8
|
+
return Math.max(0, Math.min(1, n));
|
|
9
|
+
}
|
|
10
|
+
async function affirmCommand(options) {
|
|
11
|
+
const { initDb, getMemoryById, updateMemoryLifecycle, suppressProposedSiblings, } = (0, persistence_config_1.getPersistence)();
|
|
12
|
+
await initDb();
|
|
13
|
+
const existing = await getMemoryById(options.id);
|
|
14
|
+
if (!existing) {
|
|
15
|
+
throw new Error(`Memory not found: ${options.id}`);
|
|
16
|
+
}
|
|
17
|
+
let nextConfidence = existing.confidence;
|
|
18
|
+
if (existing.status !== "affirmed") {
|
|
19
|
+
nextConfidence = Math.max(nextConfidence, 0.8);
|
|
20
|
+
nextConfidence = clamp01(nextConfidence + 0.1);
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
nextConfidence = clamp01(nextConfidence + 0.05);
|
|
24
|
+
}
|
|
25
|
+
const updated = await updateMemoryLifecycle({
|
|
26
|
+
id: existing.id,
|
|
27
|
+
status: "affirmed",
|
|
28
|
+
confidence: nextConfidence,
|
|
29
|
+
});
|
|
30
|
+
console.log(`Affirmed memory: ${updated.id} (status=${updated.status}, confidence=${updated.confidence.toFixed(2)})`);
|
|
31
|
+
if (existing.symbol) {
|
|
32
|
+
const suppressed = await suppressProposedSiblings({
|
|
33
|
+
symbol: existing.symbol,
|
|
34
|
+
repoPath: existing.repo ?? null,
|
|
35
|
+
excludeId: existing.id,
|
|
36
|
+
});
|
|
37
|
+
if (suppressed > 0) {
|
|
38
|
+
console.log(` Suppressed ${suppressed} stale proposed sibling(s) for symbol: ${existing.symbol}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.canonicalizeSymbolCommand = canonicalizeSymbolCommand;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const persistence_config_1 = require("../utils/persistence-config");
|
|
10
|
+
async function canonicalizeSymbolCommand(symbol, options) {
|
|
11
|
+
const { initDb, queryMemoryBySymbol, suppressProposedSiblings } = (0, persistence_config_1.getPersistence)();
|
|
12
|
+
await initDb();
|
|
13
|
+
const repoPath = resolveRepoScope(options);
|
|
14
|
+
const canonicalParams = {
|
|
15
|
+
symbol,
|
|
16
|
+
limit: 1,
|
|
17
|
+
onlyCanonical: true,
|
|
18
|
+
};
|
|
19
|
+
if (repoPath)
|
|
20
|
+
canonicalParams.repoPath = repoPath;
|
|
21
|
+
const canonical = await queryMemoryBySymbol(canonicalParams);
|
|
22
|
+
const canonicalRecord = canonical[0];
|
|
23
|
+
if (!canonicalRecord) {
|
|
24
|
+
console.log(`No affirmed record found for symbol: ${symbol}`);
|
|
25
|
+
console.log(`Tip: run "kodingo affirm <id>" to promote a record to canonical truth first.`);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const allParams = {
|
|
29
|
+
symbol,
|
|
30
|
+
limit: 100,
|
|
31
|
+
onlyCanonical: false,
|
|
32
|
+
includeDenied: false,
|
|
33
|
+
};
|
|
34
|
+
if (repoPath)
|
|
35
|
+
allParams.repoPath = repoPath;
|
|
36
|
+
const all = await queryMemoryBySymbol(allParams);
|
|
37
|
+
const proposedSiblings = all.filter((r) => r.status === "proposed" && r.id !== canonicalRecord.id);
|
|
38
|
+
const ignoredCount = all.filter((r) => r.status === "ignored" && r.id !== canonicalRecord.id).length;
|
|
39
|
+
const contentPreview = canonicalRecord.content.length > 80
|
|
40
|
+
? canonicalRecord.content.slice(0, 80) + "..."
|
|
41
|
+
: canonicalRecord.content;
|
|
42
|
+
console.log(`Symbol: ${symbol}`);
|
|
43
|
+
console.log(`${"─".repeat(60)}`);
|
|
44
|
+
console.log(`✓ Canonical record: ${canonicalRecord.id} (confidence ${Math.round(canonicalRecord.confidence * 100)}%)`);
|
|
45
|
+
console.log(` ${contentPreview}`);
|
|
46
|
+
console.log("");
|
|
47
|
+
if (proposedSiblings.length === 0) {
|
|
48
|
+
console.log(`Already clean — no stale proposed siblings found.`);
|
|
49
|
+
if (ignoredCount > 0) {
|
|
50
|
+
console.log(` ${ignoredCount} ignored record(s) retained (not canonical, not false).`);
|
|
51
|
+
}
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
console.log(`Found ${proposedSiblings.length} stale proposed sibling(s) to suppress:`);
|
|
55
|
+
for (const r of proposedSiblings) {
|
|
56
|
+
const preview = r.content.length > 60 ? r.content.slice(0, 60) + "..." : r.content;
|
|
57
|
+
console.log(` - ${r.id} (confidence ${Math.round(r.confidence * 100)}%)`);
|
|
58
|
+
console.log(` ${preview}`);
|
|
59
|
+
}
|
|
60
|
+
console.log("");
|
|
61
|
+
if (options.dryRun) {
|
|
62
|
+
console.log(`Dry run — no changes made. Remove --dry-run to apply suppression.`);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const suppressed = await suppressProposedSiblings({
|
|
66
|
+
symbol,
|
|
67
|
+
repoPath: repoPath ?? null,
|
|
68
|
+
excludeId: canonicalRecord.id,
|
|
69
|
+
});
|
|
70
|
+
console.log(`Suppressed ${suppressed} proposed sibling(s) → marked ignored.`);
|
|
71
|
+
if (ignoredCount > 0) {
|
|
72
|
+
console.log(` ${ignoredCount} previously ignored record(s) left untouched.`);
|
|
73
|
+
}
|
|
74
|
+
console.log(`Symbol memory is now clean.`);
|
|
75
|
+
}
|
|
76
|
+
function resolveRepoScope(options) {
|
|
77
|
+
if (options.global === true)
|
|
78
|
+
return undefined;
|
|
79
|
+
if (options.repo && options.repo.trim()) {
|
|
80
|
+
return path_1.default.resolve(options.repo.trim());
|
|
81
|
+
}
|
|
82
|
+
return findRepoRoot(process.cwd());
|
|
83
|
+
}
|
|
84
|
+
function findRepoRoot(startPath) {
|
|
85
|
+
let current = startPath;
|
|
86
|
+
while (true) {
|
|
87
|
+
const gitPath = path_1.default.join(current, ".git");
|
|
88
|
+
if (fs_1.default.existsSync(gitPath))
|
|
89
|
+
return current;
|
|
90
|
+
const parent = path_1.default.dirname(current);
|
|
91
|
+
if (parent === current)
|
|
92
|
+
return startPath;
|
|
93
|
+
current = parent;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.captureCommand = captureCommand;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const persistence_config_1 = require("../utils/persistence-config");
|
|
10
|
+
async function captureCommand(options) {
|
|
11
|
+
const { initDb, saveMemory } = (0, persistence_config_1.getPersistence)();
|
|
12
|
+
await initDb();
|
|
13
|
+
const content = options.content ?? (await readStdin());
|
|
14
|
+
if (!content) {
|
|
15
|
+
throw new Error("Content is required. Provide --content or stdin input.");
|
|
16
|
+
}
|
|
17
|
+
const tags = parseTags(options.tags);
|
|
18
|
+
const repoPath = findRepoRoot(process.cwd());
|
|
19
|
+
const input = {
|
|
20
|
+
type: options.type,
|
|
21
|
+
content,
|
|
22
|
+
repoPath,
|
|
23
|
+
};
|
|
24
|
+
if (options.title)
|
|
25
|
+
input.title = options.title;
|
|
26
|
+
if (tags)
|
|
27
|
+
input.tags = tags;
|
|
28
|
+
const record = await saveMemory(input);
|
|
29
|
+
console.log(`Saved memory: ${record.id} (type=${record.type})`);
|
|
30
|
+
}
|
|
31
|
+
function parseTags(tags) {
|
|
32
|
+
if (!tags)
|
|
33
|
+
return undefined;
|
|
34
|
+
const parsed = tags
|
|
35
|
+
.split(",")
|
|
36
|
+
.map((t) => t.trim())
|
|
37
|
+
.filter(Boolean);
|
|
38
|
+
return parsed.length ? parsed : undefined;
|
|
39
|
+
}
|
|
40
|
+
function findRepoRoot(startPath) {
|
|
41
|
+
let current = startPath;
|
|
42
|
+
while (true) {
|
|
43
|
+
const gitPath = path_1.default.join(current, ".git");
|
|
44
|
+
if (fs_1.default.existsSync(gitPath))
|
|
45
|
+
return current;
|
|
46
|
+
const parent = path_1.default.dirname(current);
|
|
47
|
+
if (parent === current)
|
|
48
|
+
return startPath;
|
|
49
|
+
current = parent;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async function readStdin() {
|
|
53
|
+
if (process.stdin.isTTY) {
|
|
54
|
+
console.log("Enter content, then press Ctrl+D when finished:");
|
|
55
|
+
}
|
|
56
|
+
return new Promise((resolve, reject) => {
|
|
57
|
+
let data = "";
|
|
58
|
+
process.stdin.setEncoding("utf8");
|
|
59
|
+
process.stdin.on("data", (chunk) => {
|
|
60
|
+
data += chunk;
|
|
61
|
+
});
|
|
62
|
+
process.stdin.on("end", () => {
|
|
63
|
+
resolve(data.trim());
|
|
64
|
+
});
|
|
65
|
+
process.stdin.on("error", reject);
|
|
66
|
+
});
|
|
67
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.denyCommand = denyCommand;
|
|
4
|
+
const persistence_config_1 = require("../utils/persistence-config");
|
|
5
|
+
function parseTags(tags) {
|
|
6
|
+
if (!tags)
|
|
7
|
+
return undefined;
|
|
8
|
+
const parsed = tags
|
|
9
|
+
.split(",")
|
|
10
|
+
.map((t) => t.trim())
|
|
11
|
+
.filter(Boolean);
|
|
12
|
+
return parsed.length ? parsed : undefined;
|
|
13
|
+
}
|
|
14
|
+
function mergeTags(existing, override) {
|
|
15
|
+
if (override && override.length) {
|
|
16
|
+
const base = new Set(override);
|
|
17
|
+
for (const t of existing ?? []) {
|
|
18
|
+
if (t.startsWith("signal:") || t.startsWith("kind:"))
|
|
19
|
+
base.add(t);
|
|
20
|
+
}
|
|
21
|
+
return Array.from(base);
|
|
22
|
+
}
|
|
23
|
+
if (existing && existing.length)
|
|
24
|
+
return existing;
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
async function denyCommand(options) {
|
|
28
|
+
const { initDb, getMemoryById, saveMemory, updateMemoryLifecycle } = (0, persistence_config_1.getPersistence)();
|
|
29
|
+
await initDb();
|
|
30
|
+
const existing = await getMemoryById(options.id);
|
|
31
|
+
if (!existing) {
|
|
32
|
+
throw new Error(`Memory not found: ${options.id}`);
|
|
33
|
+
}
|
|
34
|
+
const correctionContent = options.content?.trim();
|
|
35
|
+
if (!correctionContent) {
|
|
36
|
+
throw new Error("Correction content is required. Provide --content with the corrected truth.");
|
|
37
|
+
}
|
|
38
|
+
const userTags = parseTags(options.tags);
|
|
39
|
+
const tags = mergeTags(existing.tags, userTags);
|
|
40
|
+
const input = {
|
|
41
|
+
type: existing.type,
|
|
42
|
+
content: correctionContent,
|
|
43
|
+
status: "proposed",
|
|
44
|
+
confidence: 0.7,
|
|
45
|
+
correctsId: existing.id,
|
|
46
|
+
};
|
|
47
|
+
if (existing.repo)
|
|
48
|
+
input.repoPath = existing.repo;
|
|
49
|
+
if (existing.symbol)
|
|
50
|
+
input.symbol = existing.symbol;
|
|
51
|
+
const nextTitle = options.title?.trim();
|
|
52
|
+
if (nextTitle)
|
|
53
|
+
input.title = nextTitle;
|
|
54
|
+
if (tags && tags.length > 0)
|
|
55
|
+
input.tags = tags;
|
|
56
|
+
const corrected = await saveMemory(input);
|
|
57
|
+
const denied = await updateMemoryLifecycle({
|
|
58
|
+
id: existing.id,
|
|
59
|
+
status: "denied",
|
|
60
|
+
confidence: Math.min(existing.confidence, 0.2),
|
|
61
|
+
correctedById: corrected.id,
|
|
62
|
+
});
|
|
63
|
+
console.log(`Denied memory: ${denied.id} (status=${denied.status}, confidence=${denied.confidence.toFixed(2)})`);
|
|
64
|
+
console.log(` corrected_by_id: ${denied.correctedById}`);
|
|
65
|
+
console.log(`Created corrected memory: ${corrected.id} (status=${corrected.status}, confidence=${corrected.confidence.toFixed(2)})`);
|
|
66
|
+
console.log(` corrects_id: ${corrected.correctsId}`);
|
|
67
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.doctorCommand = doctorCommand;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const persistence_config_1 = require("../utils/persistence-config");
|
|
10
|
+
// checkDatabaseConnection is local-only — only used in local mode checks.
|
|
11
|
+
const memory_persistence_1 = require("../adapters/db-adapter/memory-persistence");
|
|
12
|
+
async function doctorCommand() {
|
|
13
|
+
const results = [];
|
|
14
|
+
const recordId = await runDoctor(results);
|
|
15
|
+
const hasFail = results.some((r) => r.ok === false);
|
|
16
|
+
const overallLabel = hasFail ? "FAILED" : "OK";
|
|
17
|
+
console.log(`Kodingo doctor report: ${overallLabel}`);
|
|
18
|
+
for (const r of results) {
|
|
19
|
+
if (r.ok === true)
|
|
20
|
+
console.log(`- OK: ${r.name}`);
|
|
21
|
+
else if (r.ok === "warn")
|
|
22
|
+
console.log(`- WARN: ${r.name} (${r.message})`);
|
|
23
|
+
else
|
|
24
|
+
console.log(`- FAIL: ${r.name} (${r.error})`);
|
|
25
|
+
}
|
|
26
|
+
// Clean up smoke test record if one was created
|
|
27
|
+
if (recordId) {
|
|
28
|
+
try {
|
|
29
|
+
const { deleteMemoryById } = (0, persistence_config_1.getPersistence)();
|
|
30
|
+
await deleteMemoryById(recordId);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
// ignore cleanup errors in doctor
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (hasFail)
|
|
37
|
+
process.exitCode = 1;
|
|
38
|
+
}
|
|
39
|
+
async function runDoctor(results) {
|
|
40
|
+
const { initDb, saveMemory, queryMemory } = (0, persistence_config_1.getPersistence)();
|
|
41
|
+
const cloudMode = (0, persistence_config_1.isCloudMode)();
|
|
42
|
+
// ── Mode-specific connectivity checks ──────────────────────────────────────
|
|
43
|
+
if (cloudMode) {
|
|
44
|
+
await runCloudChecks(results);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
await runLocalChecks(results);
|
|
48
|
+
}
|
|
49
|
+
// ── Shared: init + smoke test ───────────────────────────────────────────────
|
|
50
|
+
let tempId = null;
|
|
51
|
+
await runStep("Initialize database", async () => {
|
|
52
|
+
await initDb();
|
|
53
|
+
}, results);
|
|
54
|
+
await runStep("Insert temporary memory record", async () => {
|
|
55
|
+
const inserted = await saveMemory({
|
|
56
|
+
type: "note",
|
|
57
|
+
title: "smoke: doctor",
|
|
58
|
+
content: "doctor saved",
|
|
59
|
+
tags: ["smoke", "doctor"],
|
|
60
|
+
repoPath: process.cwd(),
|
|
61
|
+
});
|
|
62
|
+
tempId = inserted.id;
|
|
63
|
+
}, results);
|
|
64
|
+
await runStep("Query temporary memory record", async () => {
|
|
65
|
+
const found = await queryMemory("smoke: doctor", 5);
|
|
66
|
+
if (!found.some((r) => r.id === tempId)) {
|
|
67
|
+
throw new Error("Temporary record not found in query results");
|
|
68
|
+
}
|
|
69
|
+
}, results);
|
|
70
|
+
await runStep("Delete temporary memory record", async () => {
|
|
71
|
+
if (!tempId)
|
|
72
|
+
throw new Error("No temporary record id available");
|
|
73
|
+
const { deleteMemoryById } = (0, persistence_config_1.getPersistence)();
|
|
74
|
+
await deleteMemoryById(tempId);
|
|
75
|
+
tempId = null; // already deleted — skip outer cleanup
|
|
76
|
+
}, results);
|
|
77
|
+
// ── Shared: environment checks ──────────────────────────────────────────────
|
|
78
|
+
if (process.env.ANTHROPIC_API_KEY) {
|
|
79
|
+
results.push({ name: "ANTHROPIC_API_KEY is set", ok: true });
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
results.push({
|
|
83
|
+
name: "ANTHROPIC_API_KEY is not set",
|
|
84
|
+
ok: "warn",
|
|
85
|
+
message: "git inference will use fallback summaries",
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
const repoRoot = findRepoRoot(process.cwd());
|
|
89
|
+
const hookPath = path_1.default.join(repoRoot, ".git", "hooks", "post-commit");
|
|
90
|
+
if (fs_1.default.existsSync(hookPath) &&
|
|
91
|
+
fs_1.default.readFileSync(hookPath, "utf-8").includes("# <kodingo>")) {
|
|
92
|
+
results.push({ name: "Git hook installed", ok: true });
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
results.push({
|
|
96
|
+
name: "Git hook not installed",
|
|
97
|
+
ok: "warn",
|
|
98
|
+
message: "run kodingo install-hook to enable automatic memory ingestion",
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
return tempId;
|
|
102
|
+
}
|
|
103
|
+
// ── Local mode checks ─────────────────────────────────────────────────────────
|
|
104
|
+
async function runLocalChecks(results) {
|
|
105
|
+
await runStep("DATABASE_URL is set", async () => {
|
|
106
|
+
if (!process.env.DATABASE_URL) {
|
|
107
|
+
throw new Error("DATABASE_URL is not set");
|
|
108
|
+
}
|
|
109
|
+
}, results);
|
|
110
|
+
await runStep("Postgres connection", async () => {
|
|
111
|
+
await (0, memory_persistence_1.checkDatabaseConnection)();
|
|
112
|
+
}, results);
|
|
113
|
+
}
|
|
114
|
+
// ── Cloud mode checks ─────────────────────────────────────────────────────────
|
|
115
|
+
async function runCloudChecks(results) {
|
|
116
|
+
const config = (0, persistence_config_1.readConfig)();
|
|
117
|
+
await runStep("KODINGO_API_URL is configured", async () => {
|
|
118
|
+
if (!config.apiUrl) {
|
|
119
|
+
throw new Error("apiUrl is not set in ~/.kodingo/config.json");
|
|
120
|
+
}
|
|
121
|
+
}, results);
|
|
122
|
+
await runStep("KODINGO_API_TOKEN is configured", async () => {
|
|
123
|
+
if (!config.token) {
|
|
124
|
+
throw new Error("token is not set in ~/.kodingo/config.json");
|
|
125
|
+
}
|
|
126
|
+
}, results);
|
|
127
|
+
await runStep("kodingo-api reachable", async () => {
|
|
128
|
+
const apiUrl = config.apiUrl.replace(/\/$/, "");
|
|
129
|
+
const res = await fetch(`${apiUrl}/health`);
|
|
130
|
+
if (!res.ok) {
|
|
131
|
+
throw new Error(`Health check failed: HTTP ${res.status}`);
|
|
132
|
+
}
|
|
133
|
+
}, results);
|
|
134
|
+
await runStep("kodingo-api token valid", async () => {
|
|
135
|
+
const apiUrl = config.apiUrl.replace(/\/$/, "");
|
|
136
|
+
const res = await fetch(`${apiUrl}/memory?limit=1`, {
|
|
137
|
+
headers: { "X-Kodingo-Token": config.token },
|
|
138
|
+
});
|
|
139
|
+
if (res.status === 401) {
|
|
140
|
+
throw new Error("Token is invalid or expired");
|
|
141
|
+
}
|
|
142
|
+
if (!res.ok) {
|
|
143
|
+
throw new Error(`Unexpected response: HTTP ${res.status}`);
|
|
144
|
+
}
|
|
145
|
+
}, results);
|
|
146
|
+
}
|
|
147
|
+
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
148
|
+
function findRepoRoot(startPath) {
|
|
149
|
+
let current = startPath;
|
|
150
|
+
while (true) {
|
|
151
|
+
const gitPath = path_1.default.join(current, ".git");
|
|
152
|
+
if (fs_1.default.existsSync(gitPath))
|
|
153
|
+
return current;
|
|
154
|
+
const parent = path_1.default.dirname(current);
|
|
155
|
+
if (parent === current)
|
|
156
|
+
return startPath;
|
|
157
|
+
current = parent;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
async function runStep(name, fn, results) {
|
|
161
|
+
try {
|
|
162
|
+
await fn();
|
|
163
|
+
results.push({ name, ok: true });
|
|
164
|
+
}
|
|
165
|
+
catch (err) {
|
|
166
|
+
results.push({ name, ok: false, error: String(err?.message ?? err) });
|
|
167
|
+
}
|
|
168
|
+
}
|