@elaraai/east-plugin 0.0.0-stage → 1.0.85
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/LICENSE.md +682 -0
- package/README.md +104 -2
- package/dist/daemon/east-py-lsp.d.ts +2 -0
- package/dist/daemon/east-py-lsp.d.ts.map +1 -0
- package/dist/daemon/east-py-lsp.js +9 -0
- package/dist/daemon/east-py-lsp.js.map +1 -0
- package/dist/daemon/lsp.d.ts +2 -0
- package/dist/daemon/lsp.d.ts.map +1 -0
- package/dist/daemon/lsp.js +7 -0
- package/dist/daemon/lsp.js.map +1 -0
- package/dist/daemon/server.d.ts +2 -0
- package/dist/daemon/server.d.ts.map +1 -0
- package/dist/daemon/server.js +71 -0
- package/dist/daemon/server.js.map +1 -0
- package/dist/hooks/diagnose-bash.d.ts +2 -0
- package/dist/hooks/diagnose-bash.d.ts.map +1 -0
- package/dist/hooks/diagnose-bash.js +28 -0
- package/dist/hooks/diagnose-bash.js.map +1 -0
- package/dist/hooks/diagnose.d.ts +2 -0
- package/dist/hooks/diagnose.d.ts.map +1 -0
- package/dist/hooks/diagnose.js +33 -0
- package/dist/hooks/diagnose.js.map +1 -0
- package/dist/hooks/pre-read.d.ts +2 -0
- package/dist/hooks/pre-read.d.ts.map +1 -0
- package/dist/hooks/pre-read.js +11 -0
- package/dist/hooks/pre-read.js.map +1 -0
- package/dist/hooks/pre-write.d.ts +2 -0
- package/dist/hooks/pre-write.d.ts.map +1 -0
- package/dist/hooks/pre-write.js +11 -0
- package/dist/hooks/pre-write.js.map +1 -0
- package/dist/hooks/session-start.d.ts +2 -0
- package/dist/hooks/session-start.d.ts.map +1 -0
- package/dist/hooks/session-start.js +34 -0
- package/dist/hooks/session-start.js.map +1 -0
- package/dist/hooks/subagent-start.d.ts +2 -0
- package/dist/hooks/subagent-start.d.ts.map +1 -0
- package/dist/hooks/subagent-start.js +29 -0
- package/dist/hooks/subagent-start.js.map +1 -0
- package/dist/lib/bash-writes.d.ts +11 -0
- package/dist/lib/bash-writes.d.ts.map +1 -0
- package/dist/lib/bash-writes.js +74 -0
- package/dist/lib/bash-writes.js.map +1 -0
- package/dist/lib/codex-hook.d.ts +62 -0
- package/dist/lib/codex-hook.d.ts.map +1 -0
- package/dist/lib/codex-hook.js +93 -0
- package/dist/lib/codex-hook.js.map +1 -0
- package/dist/lib/codex-skills.d.ts +38 -0
- package/dist/lib/codex-skills.d.ts.map +1 -0
- package/dist/lib/codex-skills.js +80 -0
- package/dist/lib/codex-skills.js.map +1 -0
- package/dist/lib/codex-tools.d.ts +11 -0
- package/dist/lib/codex-tools.d.ts.map +1 -0
- package/dist/lib/codex-tools.js +41 -0
- package/dist/lib/codex-tools.js.map +1 -0
- package/dist/lib/diagnostics-client.d.ts +9 -0
- package/dist/lib/diagnostics-client.d.ts.map +1 -0
- package/dist/lib/diagnostics-client.js +123 -0
- package/dist/lib/diagnostics-client.js.map +1 -0
- package/dist/lib/east-index.d.ts +3 -0
- package/dist/lib/east-index.d.ts.map +1 -0
- package/dist/lib/east-index.js +9 -0
- package/dist/lib/east-index.js.map +1 -0
- package/dist/lib/east-project.d.ts +49 -0
- package/dist/lib/east-project.d.ts.map +1 -0
- package/dist/lib/east-project.js +121 -0
- package/dist/lib/east-project.js.map +1 -0
- package/dist/lib/east-py-lint.d.ts +7 -0
- package/dist/lib/east-py-lint.d.ts.map +1 -0
- package/dist/lib/east-py-lint.js +22 -0
- package/dist/lib/east-py-lint.js.map +1 -0
- package/dist/lib/east-rules-context.d.ts +7 -0
- package/dist/lib/east-rules-context.d.ts.map +1 -0
- package/dist/lib/east-rules-context.js +91 -0
- package/dist/lib/east-rules-context.js.map +1 -0
- package/dist/lib/hook-io.d.ts +30 -0
- package/dist/lib/hook-io.d.ts.map +1 -0
- package/dist/lib/hook-io.js +29 -0
- package/dist/lib/hook-io.js.map +1 -0
- package/dist/lib/host.d.ts +3 -0
- package/dist/lib/host.d.ts.map +1 -0
- package/dist/lib/host.js +9 -0
- package/dist/lib/host.js.map +1 -0
- package/dist/lib/lsp-client.d.ts +34 -0
- package/dist/lib/lsp-client.d.ts.map +1 -0
- package/dist/lib/lsp-client.js +200 -0
- package/dist/lib/lsp-client.js.map +1 -0
- package/dist/lib/plugin-status.d.ts +9 -0
- package/dist/lib/plugin-status.d.ts.map +1 -0
- package/dist/lib/plugin-status.js +164 -0
- package/dist/lib/plugin-status.js.map +1 -0
- package/dist/lib/review.d.ts +16 -0
- package/dist/lib/review.d.ts.map +1 -0
- package/dist/lib/review.js +74 -0
- package/dist/lib/review.js.map +1 -0
- package/dist/lib/search-guidance.d.ts +44 -0
- package/dist/lib/search-guidance.d.ts.map +1 -0
- package/dist/lib/search-guidance.js +73 -0
- package/dist/lib/search-guidance.js.map +1 -0
- package/dist/lib/search-hooks.d.ts +75 -0
- package/dist/lib/search-hooks.d.ts.map +1 -0
- package/dist/lib/search-hooks.js +117 -0
- package/dist/lib/search-hooks.js.map +1 -0
- package/dist/lib/search.d.ts +108 -0
- package/dist/lib/search.d.ts.map +1 -0
- package/dist/lib/search.js +167 -0
- package/dist/lib/search.js.map +1 -0
- package/dist/lib/transcript.d.ts +29 -0
- package/dist/lib/transcript.d.ts.map +1 -0
- package/dist/lib/transcript.js +109 -0
- package/dist/lib/transcript.js.map +1 -0
- package/dist/mcp/example-tools.d.ts +26 -0
- package/dist/mcp/example-tools.d.ts.map +1 -0
- package/dist/mcp/example-tools.js +94 -0
- package/dist/mcp/example-tools.js.map +1 -0
- package/dist/mcp/server.d.ts +2 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +51 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/scripts/examples-file.d.ts +38 -0
- package/dist/scripts/examples-file.d.ts.map +1 -0
- package/dist/scripts/examples-file.js +224 -0
- package/dist/scripts/examples-file.js.map +1 -0
- package/dist/scripts/generate-index.d.ts +127 -0
- package/dist/scripts/generate-index.d.ts.map +1 -0
- package/dist/scripts/generate-index.js +315 -0
- package/dist/scripts/generate-index.js.map +1 -0
- package/dist/status/report.d.ts +2 -0
- package/dist/status/report.d.ts.map +1 -0
- package/dist/status/report.js +9 -0
- package/dist/status/report.js.map +1 -0
- package/index.json +44929 -0
- package/package.json +66 -4
- package/scripts/render-python.py +67 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface FeatureCheck {
|
|
2
|
+
name: string;
|
|
3
|
+
status: "ok" | "warn" | "fail";
|
|
4
|
+
detail: string;
|
|
5
|
+
}
|
|
6
|
+
/** Inspect the installed plugin + the current project; never throws. */
|
|
7
|
+
export declare function checkPluginStatus(pluginRoot: string, cwd: string): Promise<FeatureCheck[]>;
|
|
8
|
+
export declare function formatStatus(checks: FeatureCheck[]): string;
|
|
9
|
+
//# sourceMappingURL=plugin-status.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin-status.d.ts","sourceRoot":"","sources":["../../lib/plugin-status.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,CAAC;IAC/B,MAAM,EAAE,MAAM,CAAC;CAChB;AA2CD,wEAAwE;AACxE,wBAAsB,iBAAiB,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAgHhG;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,CAM3D"}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
import { buildSearchIndex } from "./search.js";
|
|
5
|
+
import { getEastProjectInfo } from "./east-project.js";
|
|
6
|
+
import { execFile } from "node:child_process";
|
|
7
|
+
import { findEastPy } from "@elaraai/east-diagnostics";
|
|
8
|
+
const ICON = { ok: "✓", warn: "⚠", fail: "✗" };
|
|
9
|
+
function check(name, fn) {
|
|
10
|
+
return Promise.resolve()
|
|
11
|
+
.then(fn)
|
|
12
|
+
.catch((e) => ({ name, status: "fail", detail: `check errored: ${String(e).slice(0, 100)}` }));
|
|
13
|
+
}
|
|
14
|
+
function resolves(fromDir, spec) {
|
|
15
|
+
try {
|
|
16
|
+
// Absolute base so module resolution works even when given a relative cwd.
|
|
17
|
+
return createRequire(resolve(fromDir, "_.js")).resolve(spec);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function nearestTsconfig(fromDir) {
|
|
24
|
+
let dir = resolve(fromDir);
|
|
25
|
+
for (;;) {
|
|
26
|
+
const candidate = join(dir, "tsconfig.json");
|
|
27
|
+
if (existsSync(candidate))
|
|
28
|
+
return candidate;
|
|
29
|
+
const parent = dirname(dir);
|
|
30
|
+
if (parent === dir)
|
|
31
|
+
return undefined;
|
|
32
|
+
dir = parent;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const BUNDLED = [
|
|
36
|
+
".build/hooks/session-start.js",
|
|
37
|
+
".build/hooks/subagent-start.js",
|
|
38
|
+
".build/hooks/pre-write.js",
|
|
39
|
+
".build/hooks/pre-read.js",
|
|
40
|
+
".build/hooks/diagnose.js",
|
|
41
|
+
".build/hooks/diagnose-bash.js",
|
|
42
|
+
".build/daemon/server.js",
|
|
43
|
+
".build/daemon/lsp.js",
|
|
44
|
+
".build/daemon/east-py-lsp.js",
|
|
45
|
+
".build/mcp/server.js",
|
|
46
|
+
];
|
|
47
|
+
/** Inspect the installed plugin + the current project; never throws. */
|
|
48
|
+
export async function checkPluginStatus(pluginRoot, cwd) {
|
|
49
|
+
const checks = [];
|
|
50
|
+
checks.push(await check("Plugin", () => {
|
|
51
|
+
const pkg = JSON.parse(readFileSync(join(pluginRoot, existsSync(join(pluginRoot, ".codex-plugin")) ? ".codex-plugin" : ".claude-plugin", "plugin.json"), "utf8"));
|
|
52
|
+
return { name: "Plugin", status: "ok", detail: `version ${pkg.version ?? "?"} (${pluginRoot})` };
|
|
53
|
+
}));
|
|
54
|
+
checks.push(await check("Bundled artifacts", () => {
|
|
55
|
+
const artifacts = existsSync(join(pluginRoot, ".codex-plugin")) ? [...BUNDLED, ".build/hooks/codex-tools.js"] : BUNDLED;
|
|
56
|
+
const missing = artifacts.filter((a) => !existsSync(join(pluginRoot, a)));
|
|
57
|
+
return missing.length === 0
|
|
58
|
+
? { name: "Bundled artifacts", status: "ok", detail: `all ${artifacts.length} hook/daemon/MCP bundles present` }
|
|
59
|
+
: { name: "Bundled artifacts", status: "fail", detail: `missing ${missing.length}: ${missing.join(", ")}` };
|
|
60
|
+
}));
|
|
61
|
+
checks.push(await check("Hook configuration", () => {
|
|
62
|
+
const json = JSON.parse(readFileSync(join(pluginRoot, "hooks", "hooks.json"), "utf8"));
|
|
63
|
+
const events = Object.keys(json.hooks ?? {});
|
|
64
|
+
const diagnoseWired = /diagnose\.js|codex-tools\.js/.test(JSON.stringify(json.hooks ?? {}));
|
|
65
|
+
return {
|
|
66
|
+
name: "Hook configuration",
|
|
67
|
+
status: diagnoseWired ? "ok" : "warn",
|
|
68
|
+
detail: diagnoseWired
|
|
69
|
+
? `${events.length} events: ${events.join(", ")}`
|
|
70
|
+
: `${events.join(", ")} — PostToolUse is NOT wired to diagnose.js (stale install?)`,
|
|
71
|
+
};
|
|
72
|
+
}));
|
|
73
|
+
checks.push(await check("Example search", async () => {
|
|
74
|
+
const indexPath = join(pluginRoot, "index.json");
|
|
75
|
+
const data = JSON.parse(readFileSync(indexPath, "utf8"));
|
|
76
|
+
const entries = data.entries ?? [];
|
|
77
|
+
const programs = entries.filter((e) => e.ir !== undefined);
|
|
78
|
+
const python = programs.filter((e) => typeof e.python === "string").length;
|
|
79
|
+
const index = await buildSearchIndex(indexPath);
|
|
80
|
+
const hits = index.search("array map", { limit: 3 });
|
|
81
|
+
const ok = entries.length > 0 && hits.length > 0 && programs.length > 0 && python === programs.length;
|
|
82
|
+
return {
|
|
83
|
+
name: "Example search (index + MCP)",
|
|
84
|
+
status: ok ? "ok" : "warn",
|
|
85
|
+
detail: `${entries.length} examples indexed, ${programs.length} as IR (${python} with a python rendering); sample query → ${hits.length} hits`,
|
|
86
|
+
};
|
|
87
|
+
}));
|
|
88
|
+
checks.push(await check("Skills", () => {
|
|
89
|
+
const dirs = readdirSync(join(pluginRoot, "skills"), { withFileTypes: true })
|
|
90
|
+
.filter((d) => d.isDirectory() && existsSync(join(pluginRoot, "skills", d.name, "SKILL.md")))
|
|
91
|
+
.map((d) => d.name);
|
|
92
|
+
return { name: "Skills", status: dirs.length > 0 ? "ok" : "warn", detail: `${dirs.length}: ${dirs.join(", ")}` };
|
|
93
|
+
}));
|
|
94
|
+
checks.push(await check("East project (cwd)", async () => {
|
|
95
|
+
const { isEast, skills } = await getEastProjectInfo(cwd);
|
|
96
|
+
return {
|
|
97
|
+
name: "East project (cwd)",
|
|
98
|
+
status: isEast ? "ok" : "warn",
|
|
99
|
+
detail: isEast ? `detected: ${skills.join(", ")}` : `${cwd} is not an East project — hooks stay idle here (expected outside East projects)`,
|
|
100
|
+
};
|
|
101
|
+
}));
|
|
102
|
+
checks.push(await check("Diagnostics (python / east-py)", async () => {
|
|
103
|
+
// Without this check a missing east-py is INVISIBLE: runEastPyLint returns
|
|
104
|
+
// null, every python file silently gets no review, and the rest of this
|
|
105
|
+
// report stays green. Two questions: do the rules answer, and can the warm
|
|
106
|
+
// server (`east-py lsp`, the build tier's home) start.
|
|
107
|
+
const command = findEastPy(cwd);
|
|
108
|
+
const run = (args) => new Promise((done) => {
|
|
109
|
+
execFile(command, args, { timeout: 8000, encoding: "utf-8" }, (error, stdout, stderr) => {
|
|
110
|
+
done({ ok: error === null, stdout: String(stdout), stderr: String(stderr) });
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
const listed = await run(["lint", "--list-rules"]);
|
|
114
|
+
if (!listed.ok) {
|
|
115
|
+
return {
|
|
116
|
+
name: "Diagnostics (python / east-py)",
|
|
117
|
+
status: "warn",
|
|
118
|
+
detail: `\`${command}\` did not answer — python East files get NO review until east-py resolves (a project .venv above the file, east-py on PATH, or EAST_PY_LINT)`,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
const rules = listed.stdout.split("\n").filter((l) => l.startsWith("EAS")).length;
|
|
122
|
+
const probe = await run(["lsp", "--probe"]);
|
|
123
|
+
return probe.ok
|
|
124
|
+
? { name: "Diagnostics (python / east-py)", status: "ok", detail: `${command} — ${rules} rules; ${probe.stdout.trim()} (warm server + build tier)` }
|
|
125
|
+
: {
|
|
126
|
+
name: "Diagnostics (python / east-py)",
|
|
127
|
+
status: "warn",
|
|
128
|
+
detail: `${command} — ${rules} rules, but \`east-py lsp\` cannot start (${probe.stderr.trim().split("\n")[0] ?? "no reason given"}): the rules still run per change, the build tier is off`,
|
|
129
|
+
};
|
|
130
|
+
}));
|
|
131
|
+
checks.push(await check("Diagnostics (PostToolUse daemon)", () => {
|
|
132
|
+
const parts = [];
|
|
133
|
+
let status = "ok";
|
|
134
|
+
const tsOk = resolves(cwd, "typescript") !== undefined;
|
|
135
|
+
parts.push(tsOk ? "typescript resolvable" : "typescript NOT resolvable");
|
|
136
|
+
if (!tsOk)
|
|
137
|
+
status = "warn";
|
|
138
|
+
const tsconfig = nearestTsconfig(cwd) !== undefined;
|
|
139
|
+
parts.push(tsconfig ? "tsconfig found" : "no tsconfig near cwd");
|
|
140
|
+
if (!tsconfig)
|
|
141
|
+
status = "warn";
|
|
142
|
+
const eastOk = resolves(cwd, "@elaraai/east") !== undefined;
|
|
143
|
+
parts.push(eastOk ? "@elaraai/east resolvable (built)" : "@elaraai/east not resolvable/built");
|
|
144
|
+
const daemonOk = existsSync(join(pluginRoot, ".build/daemon/server.js"));
|
|
145
|
+
if (!daemonOk) {
|
|
146
|
+
parts.push("daemon bundle MISSING");
|
|
147
|
+
status = "fail";
|
|
148
|
+
}
|
|
149
|
+
return {
|
|
150
|
+
name: "Diagnostics (PostToolUse daemon)",
|
|
151
|
+
status,
|
|
152
|
+
detail: `${parts.join("; ")} — ${status === "ok" ? "ready" : status === "fail" ? "broken" : "limited (prereqs missing)"}`,
|
|
153
|
+
};
|
|
154
|
+
}));
|
|
155
|
+
return checks;
|
|
156
|
+
}
|
|
157
|
+
export function formatStatus(checks) {
|
|
158
|
+
const anyFail = checks.some((c) => c.status === "fail");
|
|
159
|
+
const anyWarn = checks.some((c) => c.status === "warn");
|
|
160
|
+
const header = anyFail ? "East plugin status — ISSUES" : anyWarn ? "East plugin status — OK (with notes)" : "East plugin status — OK";
|
|
161
|
+
const lines = checks.map((c) => `- ${ICON[c.status]} **${c.name}**: ${c.detail}`);
|
|
162
|
+
return [`## ${header}`, "", ...lines].join("\n");
|
|
163
|
+
}
|
|
164
|
+
//# sourceMappingURL=plugin-status.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin-status.js","sourceRoot":"","sources":["../../lib/plugin-status.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Whether the plugin reviews this path at all, by extension and location. */
|
|
2
|
+
export declare function reviewable(filePath: string): boolean;
|
|
3
|
+
/**
|
|
4
|
+
* The `<east-code-review>` text for one file.
|
|
5
|
+
*
|
|
6
|
+
* `""` when the file is clean, `null` when it is not East source, not
|
|
7
|
+
* reviewable, or no reviewer answered within budget — a transient miss must
|
|
8
|
+
* stay silent AND stay un-deduped so a retry can succeed.
|
|
9
|
+
*
|
|
10
|
+
* Reviews each distinct file CONTENT once per session: the marker is keyed by
|
|
11
|
+
* session + path + content hash, so re-reads of unchanged content skip while a
|
|
12
|
+
* real edit re-fires. That is also what keeps a file written by Bash and then
|
|
13
|
+
* read from being reviewed twice.
|
|
14
|
+
*/
|
|
15
|
+
export declare function reviewFile(sessionId: string, filePath: string): Promise<string | null>;
|
|
16
|
+
//# sourceMappingURL=review.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review.d.ts","sourceRoot":"","sources":["../../lib/review.ts"],"names":[],"mappings":"AAcA,8EAA8E;AAC9E,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAGpD;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAwC5F"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { existsSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join, dirname, resolve } from "node:path";
|
|
6
|
+
import { getEastProjectInfo } from "./east-project.js";
|
|
7
|
+
import { getDiagnosticsText } from "./diagnostics-client.js";
|
|
8
|
+
import { getPythonDiagnosticsText } from "./east-py-lint.js";
|
|
9
|
+
import { PYTHON_EAST_IMPORT } from "@elaraai/east-diagnostics";
|
|
10
|
+
const EAST_IMPORT_PATTERN = /@elaraai\/east/;
|
|
11
|
+
// Vendored / built / generated trees: never review code the agent doesn't own.
|
|
12
|
+
const SKIP_PATH = /[/\\](node_modules|dist|build|\.venv|\.git)[/\\]/;
|
|
13
|
+
/** Whether the plugin reviews this path at all, by extension and location. */
|
|
14
|
+
export function reviewable(filePath) {
|
|
15
|
+
if (SKIP_PATH.test(filePath))
|
|
16
|
+
return false;
|
|
17
|
+
return /\.(py|ts|tsx|js)$/.test(filePath);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The `<east-code-review>` text for one file.
|
|
21
|
+
*
|
|
22
|
+
* `""` when the file is clean, `null` when it is not East source, not
|
|
23
|
+
* reviewable, or no reviewer answered within budget — a transient miss must
|
|
24
|
+
* stay silent AND stay un-deduped so a retry can succeed.
|
|
25
|
+
*
|
|
26
|
+
* Reviews each distinct file CONTENT once per session: the marker is keyed by
|
|
27
|
+
* session + path + content hash, so re-reads of unchanged content skip while a
|
|
28
|
+
* real edit re-fires. That is also what keeps a file written by Bash and then
|
|
29
|
+
* read from being reviewed twice.
|
|
30
|
+
*/
|
|
31
|
+
export async function reviewFile(sessionId, filePath) {
|
|
32
|
+
if (!reviewable(filePath))
|
|
33
|
+
return null;
|
|
34
|
+
const python = filePath.endsWith(".py");
|
|
35
|
+
let content;
|
|
36
|
+
try {
|
|
37
|
+
content = await readFile(filePath, "utf-8");
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
if (!(python ? PYTHON_EAST_IMPORT : EAST_IMPORT_PATTERN).test(content))
|
|
43
|
+
return null;
|
|
44
|
+
// Resolve the East project from the FILE's location, NOT the session cwd:
|
|
45
|
+
// Claude is frequently launched from elsewhere while editing a project under
|
|
46
|
+
// a sibling directory. A python file's project is wherever its `.venv` is.
|
|
47
|
+
const projectDir = dirname(resolve(filePath));
|
|
48
|
+
if (!python) {
|
|
49
|
+
const { isEast } = await getEastProjectInfo(projectDir);
|
|
50
|
+
if (!isEast)
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
const key = createHash("sha1")
|
|
54
|
+
.update(`${sessionId}\0${filePath}\0`)
|
|
55
|
+
.update(content)
|
|
56
|
+
.digest("hex")
|
|
57
|
+
.slice(0, 20);
|
|
58
|
+
const marker = join(tmpdir(), `east-diag-seen-${key}`);
|
|
59
|
+
if (existsSync(marker))
|
|
60
|
+
return null;
|
|
61
|
+
const text = python
|
|
62
|
+
? await getPythonDiagnosticsText(filePath)
|
|
63
|
+
: await getDiagnosticsText(projectDir, filePath);
|
|
64
|
+
if (text === null)
|
|
65
|
+
return null; // transient — don't mark, allow a retry
|
|
66
|
+
try {
|
|
67
|
+
writeFileSync(marker, "");
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
/* best-effort dedupe */
|
|
71
|
+
}
|
|
72
|
+
return text;
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=review.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review.js","sourceRoot":"","sources":["../../lib/review.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAE/D,MAAM,mBAAmB,GAAG,gBAAgB,CAAC;AAC7C,+EAA+E;AAC/E,MAAM,SAAS,GAAG,kDAAkD,CAAC;AAErE,8EAA8E;AAC9E,MAAM,UAAU,UAAU,CAAC,QAAgB;IACzC,IAAI,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC;QAAE,OAAO,KAAK,CAAC;IAC3C,OAAO,mBAAmB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;AAC5C,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,SAAiB,EAAE,QAAgB;IAClE,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,IAAI,CAAC;IACvC,MAAM,MAAM,GAAG,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAExC,IAAI,OAAe,CAAC;IACpB,IAAI,CAAC;QACH,OAAO,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC9C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,OAAO,IAAI,CAAC;IAEpF,0EAA0E;IAC1E,6EAA6E;IAC7E,2EAA2E;IAC3E,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC9C,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,kBAAkB,CAAC,UAAU,CAAC,CAAC;QACxD,IAAI,CAAC,MAAM;YAAE,OAAO,IAAI,CAAC;IAC3B,CAAC;IAED,MAAM,GAAG,GAAG,UAAU,CAAC,MAAM,CAAC;SAC3B,MAAM,CAAC,GAAG,SAAS,KAAK,QAAQ,IAAI,CAAC;SACrC,MAAM,CAAC,OAAO,CAAC;SACf,MAAM,CAAC,KAAK,CAAC;SACb,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAChB,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,EAAE,kBAAkB,GAAG,EAAE,CAAC,CAAC;IACvD,IAAI,UAAU,CAAC,MAAM,CAAC;QAAE,OAAO,IAAI,CAAC;IAEpC,MAAM,IAAI,GAAG,MAAM;QACjB,CAAC,CAAC,MAAM,wBAAwB,CAAC,QAAQ,CAAC;QAC1C,CAAC,CAAC,MAAM,kBAAkB,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;IACnD,IAAI,IAAI,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC,CAAC,wCAAwC;IAExE,IAAI,CAAC;QACH,aAAa,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAC5B,CAAC;IAAC,MAAM,CAAC;QACP,wBAAwB;IAC1B,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/** The names the search-first guidance speaks in. */
|
|
2
|
+
export interface SearchGuidance {
|
|
3
|
+
/** The search tool as the agent calls it, e.g. `mcp__plugin_east_east__search_east_examples`. */
|
|
4
|
+
searchTool: string;
|
|
5
|
+
/** The fetch tool as the agent calls it, e.g. `mcp__plugin_east_east__get_east_example`. */
|
|
6
|
+
getTool: string;
|
|
7
|
+
/** What the code, its packages and its skills are called, e.g. `East`. */
|
|
8
|
+
corpus: string;
|
|
9
|
+
/** The npm scope whose installed packages the read reminder watches, e.g. `@elaraai`. */
|
|
10
|
+
scope: string;
|
|
11
|
+
}
|
|
12
|
+
/** East's names. */
|
|
13
|
+
export declare const EAST_GUIDANCE: SearchGuidance;
|
|
14
|
+
/**
|
|
15
|
+
* The instruction a write of corpus code meets when no search is on record.
|
|
16
|
+
*
|
|
17
|
+
* @param g - The names it speaks in
|
|
18
|
+
* @returns The gate's text
|
|
19
|
+
*/
|
|
20
|
+
export declare function gateText(g: SearchGuidance): string;
|
|
21
|
+
/**
|
|
22
|
+
* The reminder a read of the installed packages or the examples gets.
|
|
23
|
+
*
|
|
24
|
+
* @param g - The names it speaks in
|
|
25
|
+
* @returns The reminder's text
|
|
26
|
+
*/
|
|
27
|
+
export declare function readText(g: SearchGuidance): string;
|
|
28
|
+
/**
|
|
29
|
+
* Whether a Read, Grep or Glob reaches for the example corpus: the installed
|
|
30
|
+
* packages of `scope` or the `*.examples.ts` files. A project's own grep for
|
|
31
|
+
* its imports of the scope is not a sweep of the packages, and a read of one
|
|
32
|
+
* project file is never one.
|
|
33
|
+
*
|
|
34
|
+
* @param scope - The npm scope whose installed packages count, e.g. `@elaraai`
|
|
35
|
+
* @returns The check, over a tool's name and input
|
|
36
|
+
*/
|
|
37
|
+
export declare function corpusReadFor(scope: string): (tool: string, input: Record<string, unknown>) => boolean;
|
|
38
|
+
/** East's gate. */
|
|
39
|
+
export declare const GATE_TEXT: string;
|
|
40
|
+
/** East's read reminder. */
|
|
41
|
+
export declare const READ_TEXT: string;
|
|
42
|
+
/** Whether a Read, Grep or Glob reaches for East's corpus. */
|
|
43
|
+
export declare const isExampleCorpusRead: (tool: string, input: Record<string, unknown>) => boolean;
|
|
44
|
+
//# sourceMappingURL=search-guidance.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search-guidance.d.ts","sourceRoot":"","sources":["../../lib/search-guidance.ts"],"names":[],"mappings":"AAKA,qDAAqD;AACrD,MAAM,WAAW,cAAc;IAC7B,iGAAiG;IACjG,UAAU,EAAE,MAAM,CAAC;IACnB,4FAA4F;IAC5F,OAAO,EAAE,MAAM,CAAC;IAChB,0EAA0E;IAC1E,MAAM,EAAE,MAAM,CAAC;IACf,yFAAyF;IACzF,KAAK,EAAE,MAAM,CAAC;CACf;AAED,oBAAoB;AACpB,eAAO,MAAM,aAAa,EAAE,cAK3B,CAAC;AAQF;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,cAAc,GAAG,MAAM,CAQlD;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,cAAc,GAAG,MAAM,CAKlD;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAatG;AAED,mBAAmB;AACnB,eAAO,MAAM,SAAS,EAAE,MAAgC,CAAC;AAEzD,4BAA4B;AAC5B,eAAO,MAAM,SAAS,EAAE,MAAgC,CAAC;AAEzD,8DAA8D;AAC9D,eAAO,MAAM,mBAAmB,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAA4C,CAAC"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// The search-first guidance (#654): the gate a write of corpus code meets when
|
|
2
|
+
// no example search is on record, and the reminder a read of the installed
|
|
3
|
+
// packages or the examples gets. Spoken in a plugin's own names, so a plugin
|
|
4
|
+
// that builds on East's gives the same guidance over its own corpus.
|
|
5
|
+
/** East's names. */
|
|
6
|
+
export const EAST_GUIDANCE = {
|
|
7
|
+
searchTool: "mcp__plugin_east_east__search_east_examples",
|
|
8
|
+
getTool: "mcp__plugin_east_east__get_east_example",
|
|
9
|
+
corpus: "East",
|
|
10
|
+
scope: "@elaraai",
|
|
11
|
+
};
|
|
12
|
+
/** A tool's own name, without the `mcp__<server>__` prefix the agent calls it by. */
|
|
13
|
+
function bare(tool) {
|
|
14
|
+
const at = tool.lastIndexOf("__");
|
|
15
|
+
return at < 0 ? tool : tool.slice(at + 2);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The instruction a write of corpus code meets when no search is on record.
|
|
19
|
+
*
|
|
20
|
+
* @param g - The names it speaks in
|
|
21
|
+
* @returns The gate's text
|
|
22
|
+
*/
|
|
23
|
+
export function gateText(g) {
|
|
24
|
+
return [
|
|
25
|
+
`STOP: no ${g.corpus} example search on record in this session, and this is ${g.corpus} code.`,
|
|
26
|
+
`Before writing or changing ${g.corpus} code, search the tested example index — it is the API reference:`,
|
|
27
|
+
`1. \`${g.searchTool}\` with what you are about to do (language: "python" for east-py, "typescript" otherwise); summaries come back — id, signature, inputs and result.`,
|
|
28
|
+
`2. \`${g.getTool}\` for the one or two that match, and pattern your code on them.`,
|
|
29
|
+
`Do not read node_modules/${g.scope}/** or *.examples.ts files instead: the index is the same corpus, exact and far cheaper. Every ${g.corpus} skill requires this step.`,
|
|
30
|
+
].join("\n");
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The reminder a read of the installed packages or the examples gets.
|
|
34
|
+
*
|
|
35
|
+
* @param g - The names it speaks in
|
|
36
|
+
* @returns The reminder's text
|
|
37
|
+
*/
|
|
38
|
+
export function readText(g) {
|
|
39
|
+
return [
|
|
40
|
+
`Note: the ${g.corpus} example index is the API reference — \`${g.searchTool}\` (then \`${bare(g.getTool)}\`) returns the same tested programs as the ${g.corpus} packages' examples and type declarations, exact, printed in TypeScript or python, at a fraction of the tokens.`,
|
|
41
|
+
`Reading \`.d.ts\` signatures or sweeping \`*.examples.ts\` files reliably produces broken ${g.corpus} code that still type-checks: the signatures omit the runtime rules. Search instead, and read a specific file only when the search pointed you at it.`,
|
|
42
|
+
].join("\n");
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Whether a Read, Grep or Glob reaches for the example corpus: the installed
|
|
46
|
+
* packages of `scope` or the `*.examples.ts` files. A project's own grep for
|
|
47
|
+
* its imports of the scope is not a sweep of the packages, and a read of one
|
|
48
|
+
* project file is never one.
|
|
49
|
+
*
|
|
50
|
+
* @param scope - The npm scope whose installed packages count, e.g. `@elaraai`
|
|
51
|
+
* @returns The check, over a tool's name and input
|
|
52
|
+
*/
|
|
53
|
+
export function corpusReadFor(scope) {
|
|
54
|
+
const escaped = scope.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
55
|
+
const packagePath = new RegExp(`[/\\\\]node_modules[/\\\\]${escaped}[/\\\\]`);
|
|
56
|
+
// a pattern that sweeps the packages themselves (`node_modules/@elaraai/**`), as opposed to a project's own grep for its `@elaraai/east` imports
|
|
57
|
+
const packagePattern = new RegExp(`(^|[/\\\\])node_modules[/\\\\]${escaped}([/\\\\]|$)`);
|
|
58
|
+
const examplesFile = /\.examples\.tsx?$/;
|
|
59
|
+
return (tool, input) => {
|
|
60
|
+
const file = typeof input['file_path'] === 'string' ? input['file_path'] : '';
|
|
61
|
+
const dir = typeof input['path'] === 'string' ? input['path'] : '';
|
|
62
|
+
const pattern = typeof input['pattern'] === 'string' ? input['pattern'] : '';
|
|
63
|
+
return tool === 'Read' ? packagePath.test(file) || examplesFile.test(file)
|
|
64
|
+
: packagePath.test(dir) || packagePattern.test(pattern) || /\.examples\.tsx?/.test(pattern);
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/** East's gate. */
|
|
68
|
+
export const GATE_TEXT = gateText(EAST_GUIDANCE);
|
|
69
|
+
/** East's read reminder. */
|
|
70
|
+
export const READ_TEXT = readText(EAST_GUIDANCE);
|
|
71
|
+
/** Whether a Read, Grep or Glob reaches for East's corpus. */
|
|
72
|
+
export const isExampleCorpusRead = corpusReadFor(EAST_GUIDANCE.scope);
|
|
73
|
+
//# sourceMappingURL=search-guidance.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search-guidance.js","sourceRoot":"","sources":["../../lib/search-guidance.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { type HookInput } from "./hook-io.js";
|
|
2
|
+
/** What a search-first hook answers: context given to the agent, or the call refused with its reason. */
|
|
3
|
+
export interface SearchHookReply {
|
|
4
|
+
decision: "context" | "deny";
|
|
5
|
+
text: string;
|
|
6
|
+
}
|
|
7
|
+
/** The pre-write gate's configuration. */
|
|
8
|
+
export interface SearchGateOptions {
|
|
9
|
+
/** The tools whose call counts as a search: the plugin's own and, with the East plugin beside it, East's. */
|
|
10
|
+
searchTools: readonly string[];
|
|
11
|
+
/** The instruction the gate gives (`gateText(...)`). */
|
|
12
|
+
gateText: string;
|
|
13
|
+
/** Whether a file's code is the corpus's: it imports the corpus's packages. */
|
|
14
|
+
isCorpusCode(code: string, python: boolean): boolean;
|
|
15
|
+
/** Whether the project at `cwd` is one the gate applies in; every project when absent. */
|
|
16
|
+
isProject?(cwd: string, python: boolean): Promise<boolean>;
|
|
17
|
+
/** The prefix of the per-session marker in the temporary directory that remembers a search seen. */
|
|
18
|
+
markerPrefix: string;
|
|
19
|
+
/** The environment variable that makes the gate refuse the write when it is `deny`. */
|
|
20
|
+
requireSearchEnv: string;
|
|
21
|
+
}
|
|
22
|
+
/** East's gate. */
|
|
23
|
+
export declare const EAST_SEARCH_GATE: SearchGateOptions;
|
|
24
|
+
/**
|
|
25
|
+
* The pre-write gate's answer to an Edit or Write: the instruction to search
|
|
26
|
+
* first when it writes corpus code and no search is on record, else nothing. A
|
|
27
|
+
* reminder, or a refusal when the options' environment variable is `deny`.
|
|
28
|
+
* Once a search is seen, a per-session marker keeps every later write silent,
|
|
29
|
+
* so a long transcript is read at most once.
|
|
30
|
+
*
|
|
31
|
+
* @param event - The PreToolUse event
|
|
32
|
+
* @param options - The gate's configuration
|
|
33
|
+
* @returns The reply, or null when the write passes
|
|
34
|
+
*/
|
|
35
|
+
export declare function preWriteGate(event: HookInput, options: SearchGateOptions): Promise<SearchHookReply | null>;
|
|
36
|
+
/** The read reminder's configuration. */
|
|
37
|
+
export interface ReadReminderOptions {
|
|
38
|
+
/** The reminder (`readText(...)`). */
|
|
39
|
+
readText: string;
|
|
40
|
+
/** Whether a Read, Grep or Glob reaches for the corpus (`corpusReadFor(scope)`). */
|
|
41
|
+
isCorpusRead(tool: string, input: Record<string, unknown>): boolean;
|
|
42
|
+
}
|
|
43
|
+
/** East's reminder. */
|
|
44
|
+
export declare const EAST_READ_REMINDER: ReadReminderOptions;
|
|
45
|
+
/**
|
|
46
|
+
* The read reminder's answer to a Read, Grep or Glob: the reminder when it
|
|
47
|
+
* reaches for the corpus, else nothing. Never a refusal.
|
|
48
|
+
*
|
|
49
|
+
* @param event - The PreToolUse event
|
|
50
|
+
* @param options - The reminder's configuration
|
|
51
|
+
* @returns The reply, or null
|
|
52
|
+
*/
|
|
53
|
+
export declare function preReadReminder(event: HookInput, options: ReadReminderOptions): SearchHookReply | null;
|
|
54
|
+
/**
|
|
55
|
+
* Writes a hook's reply to stdout, as the host reads it.
|
|
56
|
+
*
|
|
57
|
+
* @param hookEventName - The event the hook answers, e.g. `PreToolUse`
|
|
58
|
+
* @param reply - The reply
|
|
59
|
+
*/
|
|
60
|
+
export declare function writeSearchHookReply(hookEventName: string, reply: SearchHookReply): void;
|
|
61
|
+
/**
|
|
62
|
+
* Runs the pre-write gate as a hook: reads the event from stdin, writes the
|
|
63
|
+
* reply, if any, to stdout.
|
|
64
|
+
*
|
|
65
|
+
* @param options - The gate's configuration
|
|
66
|
+
*/
|
|
67
|
+
export declare function runPreWriteGate(options: SearchGateOptions): Promise<void>;
|
|
68
|
+
/**
|
|
69
|
+
* Runs the read reminder as a hook: reads the event from stdin, writes the
|
|
70
|
+
* reply, if any, to stdout.
|
|
71
|
+
*
|
|
72
|
+
* @param options - The reminder's configuration
|
|
73
|
+
*/
|
|
74
|
+
export declare function runPreReadReminder(options: ReadReminderOptions): Promise<void>;
|
|
75
|
+
//# sourceMappingURL=search-hooks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search-hooks.d.ts","sourceRoot":"","sources":["../../lib/search-hooks.ts"],"names":[],"mappings":"AAKA,OAAO,EAAqD,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAWjG,yGAAyG;AACzG,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,SAAS,GAAG,MAAM,CAAC;IAC7B,IAAI,EAAE,MAAM,CAAC;CACd;AAED,0CAA0C;AAC1C,MAAM,WAAW,iBAAiB;IAChC,6GAA6G;IAC7G,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B,wDAAwD;IACxD,QAAQ,EAAE,MAAM,CAAC;IACjB,+EAA+E;IAC/E,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC;IACrD,0FAA0F;IAC1F,SAAS,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3D,oGAAoG;IACpG,YAAY,EAAE,MAAM,CAAC;IACrB,uFAAuF;IACvF,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,mBAAmB;AACnB,eAAO,MAAM,gBAAgB,EAAE,iBAO9B,CAAC;AAEF;;;;;;;;;;GAUG;AACH,wBAAsB,YAAY,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAmChH;AAED,yCAAyC;AACzC,MAAM,WAAW,mBAAmB;IAClC,sCAAsC;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,oFAAoF;IACpF,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC;CACrE;AAED,uBAAuB;AACvB,eAAO,MAAM,kBAAkB,EAAE,mBAAgF,CAAC;AAElH;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,mBAAmB,GAAG,eAAe,GAAG,IAAI,CAEtG;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,aAAa,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,GAAG,IAAI,CAGxF;AAED;;;;;GAKG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAG/E;AAED;;;;;GAKG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAGpF"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { existsSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { PYTHON_EAST_IMPORT } from "@elaraai/east-diagnostics";
|
|
6
|
+
import { readHookInput, writeHookDecision, writeHookOutput } from "./hook-io.js";
|
|
7
|
+
import { getEastProjectInfo } from "./east-project.js";
|
|
8
|
+
import { SEARCH_TOOLS, searchedInTranscript } from "./transcript.js";
|
|
9
|
+
import { GATE_TEXT, READ_TEXT, isExampleCorpusRead } from "./search-guidance.js";
|
|
10
|
+
/** East's gate. */
|
|
11
|
+
export const EAST_SEARCH_GATE = {
|
|
12
|
+
searchTools: SEARCH_TOOLS,
|
|
13
|
+
gateText: GATE_TEXT,
|
|
14
|
+
isCorpusCode: (code, python) => (python ? PYTHON_EAST_IMPORT : /@elaraai\/east/).test(code),
|
|
15
|
+
isProject: async (cwd, python) => python || (await getEastProjectInfo(cwd)).isEast,
|
|
16
|
+
markerPrefix: "east-search-seen",
|
|
17
|
+
requireSearchEnv: "EAST_REQUIRE_SEARCH",
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* The pre-write gate's answer to an Edit or Write: the instruction to search
|
|
21
|
+
* first when it writes corpus code and no search is on record, else nothing. A
|
|
22
|
+
* reminder, or a refusal when the options' environment variable is `deny`.
|
|
23
|
+
* Once a search is seen, a per-session marker keeps every later write silent,
|
|
24
|
+
* so a long transcript is read at most once.
|
|
25
|
+
*
|
|
26
|
+
* @param event - The PreToolUse event
|
|
27
|
+
* @param options - The gate's configuration
|
|
28
|
+
* @returns The reply, or null when the write passes
|
|
29
|
+
*/
|
|
30
|
+
export async function preWriteGate(event, options) {
|
|
31
|
+
const cwd = event.cwd || process.cwd();
|
|
32
|
+
const filePath = event.tool_input?.file_path;
|
|
33
|
+
if (!filePath)
|
|
34
|
+
return null;
|
|
35
|
+
const python = filePath.endsWith(".py");
|
|
36
|
+
if (!python && !filePath.endsWith(".ts") && !filePath.endsWith(".tsx") && !filePath.endsWith(".js"))
|
|
37
|
+
return null;
|
|
38
|
+
// Is this the corpus's code? A Write carries its content; an Edit only its
|
|
39
|
+
// diff, so the file on disk (or the new text) says what it is.
|
|
40
|
+
let code = "";
|
|
41
|
+
if (event.tool_name === "Write") {
|
|
42
|
+
code = event.tool_input?.content ?? "";
|
|
43
|
+
}
|
|
44
|
+
else if (event.tool_name === "Edit") {
|
|
45
|
+
try {
|
|
46
|
+
code = await readFile(filePath, "utf-8");
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
code = event.tool_input?.new_string ?? "";
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (!options.isCorpusCode(code, python))
|
|
53
|
+
return null;
|
|
54
|
+
if (options.isProject !== undefined && !(await options.isProject(cwd, python)))
|
|
55
|
+
return null;
|
|
56
|
+
const marker = join(tmpdir(), `${options.markerPrefix}-${event.session_id}`);
|
|
57
|
+
if (existsSync(marker))
|
|
58
|
+
return null;
|
|
59
|
+
if (event.transcript_path && (await searchedInTranscript(event.transcript_path, options.searchTools))) {
|
|
60
|
+
try {
|
|
61
|
+
writeFileSync(marker, options.searchTools.join("\n"));
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
/* best-effort */
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
return { decision: process.env[options.requireSearchEnv] === "deny" ? "deny" : "context", text: options.gateText };
|
|
69
|
+
}
|
|
70
|
+
/** East's reminder. */
|
|
71
|
+
export const EAST_READ_REMINDER = { readText: READ_TEXT, isCorpusRead: isExampleCorpusRead };
|
|
72
|
+
/**
|
|
73
|
+
* The read reminder's answer to a Read, Grep or Glob: the reminder when it
|
|
74
|
+
* reaches for the corpus, else nothing. Never a refusal.
|
|
75
|
+
*
|
|
76
|
+
* @param event - The PreToolUse event
|
|
77
|
+
* @param options - The reminder's configuration
|
|
78
|
+
* @returns The reply, or null
|
|
79
|
+
*/
|
|
80
|
+
export function preReadReminder(event, options) {
|
|
81
|
+
return options.isCorpusRead(event.tool_name ?? "", event.tool_input ?? {}) ? { decision: "context", text: options.readText } : null;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Writes a hook's reply to stdout, as the host reads it.
|
|
85
|
+
*
|
|
86
|
+
* @param hookEventName - The event the hook answers, e.g. `PreToolUse`
|
|
87
|
+
* @param reply - The reply
|
|
88
|
+
*/
|
|
89
|
+
export function writeSearchHookReply(hookEventName, reply) {
|
|
90
|
+
if (reply.decision === "deny")
|
|
91
|
+
writeHookDecision(hookEventName, "deny", reply.text);
|
|
92
|
+
else
|
|
93
|
+
writeHookOutput(hookEventName, reply.text);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Runs the pre-write gate as a hook: reads the event from stdin, writes the
|
|
97
|
+
* reply, if any, to stdout.
|
|
98
|
+
*
|
|
99
|
+
* @param options - The gate's configuration
|
|
100
|
+
*/
|
|
101
|
+
export async function runPreWriteGate(options) {
|
|
102
|
+
const reply = await preWriteGate(await readHookInput(), options);
|
|
103
|
+
if (reply !== null)
|
|
104
|
+
writeSearchHookReply("PreToolUse", reply);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Runs the read reminder as a hook: reads the event from stdin, writes the
|
|
108
|
+
* reply, if any, to stdout.
|
|
109
|
+
*
|
|
110
|
+
* @param options - The reminder's configuration
|
|
111
|
+
*/
|
|
112
|
+
export async function runPreReadReminder(options) {
|
|
113
|
+
const reply = preReadReminder(await readHookInput(), options);
|
|
114
|
+
if (reply !== null)
|
|
115
|
+
writeSearchHookReply("PreToolUse", reply);
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=search-hooks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search-hooks.js","sourceRoot":"","sources":["../../lib/search-hooks.ts"],"names":[],"mappings":"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"}
|