-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcortex_hook_main.js
More file actions
112 lines (105 loc) · 4.28 KB
/
Copy pathcortex_hook_main.js
File metadata and controls
112 lines (105 loc) · 4.28 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
#!/usr/bin/env node
// forge cortex hook entrypoint — invoked by the shell hooks with the hook JSON on stdin.
// FAIL-SAFE BY CONSTRUCTION: any error is swallowed and the process exits 0, so Cortex can
// never block or break a tool call or a session. It is advisory memory, nothing more.
//
// modes: capture (PostToolUse Edit|Write|Bash) — log a signal event
// prompt (UserPromptSubmit) — log a user-utterance event
// stop (Stop) — distill the session into lessons
// session-start (SessionStart) — inject learned lessons as context
import { applyDistillation, lessonsForContext, startupBlock } from "./cortex.js";
import {
appendSessionEvent,
classifyEvent,
clearSession,
processSession,
readSession,
} from "./cortex_hook.js";
import { load } from "./lessons_store.js";
import { clarifyBlock, preflightRepo } from "./preflight.js";
// Opt-in: distill newly-created lessons into real prose via a cheap model call. Off by
// default (deterministic template is used); fail-safe (any error → keep the template).
async function enrichCreated(root, results) {
if (process.env.ENABLE_CORTEX_DISTILL !== "1") return;
const created = results.filter((r) => r?.action === "created" && r.id);
if (!created.length) return;
const { distill } = await import("./cortex_distill.js");
for (const r of created) {
const lesson = load(root).find((l) => l.id === r.id);
if (!lesson) continue;
const better = distill({
context: lesson.trigger,
signals: lesson.provenance?.signals ?? [],
});
if (better) applyDistillation(root, r.id, better);
}
}
async function readStdin() {
const chunks = [];
for await (const chunk of process.stdin) chunks.push(chunk);
return Buffer.concat(chunks).toString("utf8");
}
async function main() {
const mode = process.argv[2];
let hook = {};
try {
hook = JSON.parse((await readStdin()) || "{}");
} catch {
return; // no/garbled payload → nothing to do
}
const root = hook.cwd || process.cwd();
const sid = hook.session_id || "default";
const today = Math.floor(Date.now() / 86400000);
if (mode === "capture" || mode === "prompt") {
appendSessionEvent(root, sid, classifyEvent(hook));
} else if (mode === "stop") {
const events = readSession(root, sid);
if (events.length) {
const results = processSession(root, events, today);
clearSession(root, sid);
await enrichCreated(root, results);
}
} else if (mode === "session-start") {
const block = startupBlock(root, today);
if (block) emit("SessionStart", block);
} else if (mode === "pre-edit") {
const advice = await preEditAdvisory(root, hook.tool_input?.file_path, today);
if (advice) emit("PreToolUse", advice);
} else if (mode === "preflight") {
// Assumption detector: does the task name things the repo doesn't define?
if (typeof hook.prompt === "string" && hook.prompt.trim()) {
const block = clarifyBlock(preflightRepo(root, hook.prompt, { allowBuild: false }));
if (block) emit("UserPromptSubmit", block);
}
}
}
function emit(hookEventName, additionalContext) {
process.stdout.write(
JSON.stringify({
hookSpecificOutput: { hookEventName, additionalContext },
}),
);
}
// Advisory before an edit: surface matching lessons (cheap), and — only if none matched —
// a one-line high-risk note from the predictor. Advisory only, never blocks. Low-nag by
// design: nothing is emitted unless there's a real lesson or genuinely high risk.
async function preEditAdvisory(root, file, today) {
if (!file) return "";
const { block, selected } = lessonsForContext(
root,
{ files: [file], symbols: [], keywords: [file] },
{ nowDay: today, budget: 3 },
);
if (selected.length) return block; // learned lessons for this file win
const { featuresForEdit } = await import("./cortex_features.js");
const { riskFor } = await import("./predictor.js");
const { band } = riskFor(featuresForEdit(root, { file }, { nowDay: today }), {
mode: "heuristic",
});
return band === "high"
? `Forge Cortex — ${file} looks high-risk (churn / prior mistakes here). Re-read and check impact before editing.`
: "";
}
main()
.catch(() => {})
.finally(() => process.exit(0));