kodingo-cli 1.0.25 → 1.0.26
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/cli.js +5 -0
- package/dist/commands/check-rules.js +75 -0
- package/dist/commands/install-hook.js +22 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -21,6 +21,7 @@ const sync_1 = require("./commands/sync");
|
|
|
21
21
|
const context_1 = require("./commands/context");
|
|
22
22
|
const guardrails_1 = require("./commands/guardrails");
|
|
23
23
|
const session_brief_1 = require("./commands/session-brief");
|
|
24
|
+
const check_rules_1 = require("./commands/check-rules");
|
|
24
25
|
const aging_1 = require("./commands/aging");
|
|
25
26
|
const program = new commander_1.Command();
|
|
26
27
|
program.name("kodingo").description("Kodingo CLI").version("1.0.4");
|
|
@@ -247,4 +248,8 @@ program
|
|
|
247
248
|
.option("--prompt", "Output as prompt-ready string instead of JSON")
|
|
248
249
|
.option("--limit <n>", "Max memories to include", "30")
|
|
249
250
|
.action((opts) => (0, session_brief_1.sessionBriefCommand)({ prompt: opts.prompt, limit: parseInt(opts.limit) }));
|
|
251
|
+
program
|
|
252
|
+
.command("check-rules")
|
|
253
|
+
.description("Check staged diff against project guardrails — called by pre-commit hook")
|
|
254
|
+
.action(() => (0, check_rules_1.checkRulesCommand)());
|
|
250
255
|
program.parse(process.argv);
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.checkRulesCommand = checkRulesCommand;
|
|
4
|
+
const child_process_1 = require("child_process");
|
|
5
|
+
const persistence_config_1 = require("../utils/persistence-config");
|
|
6
|
+
const API_BASE_DEFAULT = "https://kodingo-api.onrender.com";
|
|
7
|
+
function normalizePath(p) {
|
|
8
|
+
return p.replace(/\\/g, "/").toLowerCase();
|
|
9
|
+
}
|
|
10
|
+
function getCredentials() {
|
|
11
|
+
const config = (0, persistence_config_1.readConfig)();
|
|
12
|
+
const cwd = normalizePath(process.cwd());
|
|
13
|
+
const ws = (0, persistence_config_1.readWorkspaceConfig)(cwd);
|
|
14
|
+
const token = ws?.token ?? config.token ?? "";
|
|
15
|
+
const apiUrl = ws?.apiUrl ?? config.apiUrl ?? API_BASE_DEFAULT;
|
|
16
|
+
return { token, apiUrl };
|
|
17
|
+
}
|
|
18
|
+
function getStagedDiff() {
|
|
19
|
+
try {
|
|
20
|
+
return (0, child_process_1.execSync)("git diff --cached", { encoding: "utf-8", timeout: 10000 });
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return "";
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function getProjectId(token, apiUrl) {
|
|
27
|
+
return fetch(`${apiUrl}/org/projects/by-token`, {
|
|
28
|
+
headers: { "X-Kodingo-Token": token },
|
|
29
|
+
})
|
|
30
|
+
.then(r => r.ok ? r.json() : null)
|
|
31
|
+
.then((d) => d?.project?.id ?? null)
|
|
32
|
+
.catch(() => null);
|
|
33
|
+
}
|
|
34
|
+
async function checkRulesCommand() {
|
|
35
|
+
const { token, apiUrl } = getCredentials();
|
|
36
|
+
if (!token)
|
|
37
|
+
return; // No token — silent pass, never block
|
|
38
|
+
const diff = getStagedDiff();
|
|
39
|
+
if (!diff.trim())
|
|
40
|
+
return; // Empty diff — nothing to check
|
|
41
|
+
try {
|
|
42
|
+
const projectId = await getProjectId(token, apiUrl);
|
|
43
|
+
if (!projectId)
|
|
44
|
+
return; // Can't resolve project — silent pass
|
|
45
|
+
const res = await fetch(`${apiUrl}/org/projects/${projectId}/rules/check`, {
|
|
46
|
+
method: "POST",
|
|
47
|
+
headers: { "Content-Type": "application/json", "X-Kodingo-Token": token },
|
|
48
|
+
body: JSON.stringify({ diff }),
|
|
49
|
+
});
|
|
50
|
+
if (!res.ok)
|
|
51
|
+
return; // API error — silent pass, never block on infrastructure failure
|
|
52
|
+
const data = await res.json();
|
|
53
|
+
const violations = data.violations ?? [];
|
|
54
|
+
if (!violations.length)
|
|
55
|
+
return; // All clear
|
|
56
|
+
// Hard block
|
|
57
|
+
console.error("\n╔════════════════════════════════════════════════════════════╗");
|
|
58
|
+
console.error("║ KORTEX GUARDRAIL VIOLATION — COMMIT BLOCKED ║");
|
|
59
|
+
console.error("╚════════════════════════════════════════════════════════════╝\n");
|
|
60
|
+
console.error(`${violations.length} rule${violations.length !== 1 ? "s" : ""} violated:\n`);
|
|
61
|
+
for (const v of violations) {
|
|
62
|
+
console.error(` ✕ ${v.title}`);
|
|
63
|
+
console.error(` ${v.description}`);
|
|
64
|
+
if (v.explanation)
|
|
65
|
+
console.error(` → ${v.explanation}`);
|
|
66
|
+
console.error("");
|
|
67
|
+
}
|
|
68
|
+
console.error("Fix the violations above or contact your engineering lead to update the rules.");
|
|
69
|
+
console.error("To bypass in an emergency: git commit --no-verify\n");
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return; // Any unexpected error — silent pass
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -20,6 +20,28 @@ async function installHookCommand(options) {
|
|
|
20
20
|
`"${kodingoBin}" scan-git --repo "$(git rev-parse --show-toplevel)" &`,
|
|
21
21
|
"# </kodingo>",
|
|
22
22
|
].join("\n");
|
|
23
|
+
// Pre-commit hook — runs synchronously to enforce guardrails
|
|
24
|
+
const preCommitPath = path_1.default.join(hooksDir, "pre-commit");
|
|
25
|
+
const preCommitBlock = [
|
|
26
|
+
"# <kodingo-guardrails>",
|
|
27
|
+
"# Kortex: enforce project guardrails before commit",
|
|
28
|
+
`"${kodingoBin}" check-rules`,
|
|
29
|
+
"# </kodingo-guardrails>",
|
|
30
|
+
].join("\n");
|
|
31
|
+
if (fs_1.default.existsSync(preCommitPath)) {
|
|
32
|
+
const existing = fs_1.default.readFileSync(preCommitPath, "utf-8");
|
|
33
|
+
if (!existing.includes("# <kodingo-guardrails>")) {
|
|
34
|
+
const updated = existing.endsWith("\n")
|
|
35
|
+
? existing + "\n" + preCommitBlock + "\n"
|
|
36
|
+
: existing + "\n\n" + preCommitBlock + "\n";
|
|
37
|
+
fs_1.default.writeFileSync(preCommitPath, updated, "utf-8");
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
const content = "#!/bin/sh\n\n" + preCommitBlock + "\n";
|
|
42
|
+
fs_1.default.writeFileSync(preCommitPath, content, "utf-8");
|
|
43
|
+
}
|
|
44
|
+
fs_1.default.chmodSync(preCommitPath, 0o755);
|
|
23
45
|
if (fs_1.default.existsSync(hookPath)) {
|
|
24
46
|
const existing = fs_1.default.readFileSync(hookPath, "utf-8");
|
|
25
47
|
if (existing.includes("# <kodingo>")) {
|