@elaraai/east-plugin 0.0.0-stage → 1.0.87

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.
Files changed (133) hide show
  1. package/LICENSE.md +682 -0
  2. package/README.md +104 -2
  3. package/dist/daemon/east-py-lsp.d.ts +2 -0
  4. package/dist/daemon/east-py-lsp.d.ts.map +1 -0
  5. package/dist/daemon/east-py-lsp.js +9 -0
  6. package/dist/daemon/east-py-lsp.js.map +1 -0
  7. package/dist/daemon/lsp.d.ts +2 -0
  8. package/dist/daemon/lsp.d.ts.map +1 -0
  9. package/dist/daemon/lsp.js +7 -0
  10. package/dist/daemon/lsp.js.map +1 -0
  11. package/dist/daemon/server.d.ts +2 -0
  12. package/dist/daemon/server.d.ts.map +1 -0
  13. package/dist/daemon/server.js +71 -0
  14. package/dist/daemon/server.js.map +1 -0
  15. package/dist/hooks/diagnose-bash.d.ts +2 -0
  16. package/dist/hooks/diagnose-bash.d.ts.map +1 -0
  17. package/dist/hooks/diagnose-bash.js +28 -0
  18. package/dist/hooks/diagnose-bash.js.map +1 -0
  19. package/dist/hooks/diagnose.d.ts +2 -0
  20. package/dist/hooks/diagnose.d.ts.map +1 -0
  21. package/dist/hooks/diagnose.js +33 -0
  22. package/dist/hooks/diagnose.js.map +1 -0
  23. package/dist/hooks/pre-read.d.ts +2 -0
  24. package/dist/hooks/pre-read.d.ts.map +1 -0
  25. package/dist/hooks/pre-read.js +11 -0
  26. package/dist/hooks/pre-read.js.map +1 -0
  27. package/dist/hooks/pre-write.d.ts +2 -0
  28. package/dist/hooks/pre-write.d.ts.map +1 -0
  29. package/dist/hooks/pre-write.js +11 -0
  30. package/dist/hooks/pre-write.js.map +1 -0
  31. package/dist/hooks/session-start.d.ts +2 -0
  32. package/dist/hooks/session-start.d.ts.map +1 -0
  33. package/dist/hooks/session-start.js +34 -0
  34. package/dist/hooks/session-start.js.map +1 -0
  35. package/dist/hooks/subagent-start.d.ts +2 -0
  36. package/dist/hooks/subagent-start.d.ts.map +1 -0
  37. package/dist/hooks/subagent-start.js +29 -0
  38. package/dist/hooks/subagent-start.js.map +1 -0
  39. package/dist/lib/bash-writes.d.ts +11 -0
  40. package/dist/lib/bash-writes.d.ts.map +1 -0
  41. package/dist/lib/bash-writes.js +74 -0
  42. package/dist/lib/bash-writes.js.map +1 -0
  43. package/dist/lib/codex-hook.d.ts +64 -0
  44. package/dist/lib/codex-hook.d.ts.map +1 -0
  45. package/dist/lib/codex-hook.js +94 -0
  46. package/dist/lib/codex-hook.js.map +1 -0
  47. package/dist/lib/codex-skills.d.ts +38 -0
  48. package/dist/lib/codex-skills.d.ts.map +1 -0
  49. package/dist/lib/codex-skills.js +80 -0
  50. package/dist/lib/codex-skills.js.map +1 -0
  51. package/dist/lib/codex-tools.d.ts +11 -0
  52. package/dist/lib/codex-tools.d.ts.map +1 -0
  53. package/dist/lib/codex-tools.js +41 -0
  54. package/dist/lib/codex-tools.js.map +1 -0
  55. package/dist/lib/diagnostics-client.d.ts +9 -0
  56. package/dist/lib/diagnostics-client.d.ts.map +1 -0
  57. package/dist/lib/diagnostics-client.js +123 -0
  58. package/dist/lib/diagnostics-client.js.map +1 -0
  59. package/dist/lib/east-index.d.ts +3 -0
  60. package/dist/lib/east-index.d.ts.map +1 -0
  61. package/dist/lib/east-index.js +9 -0
  62. package/dist/lib/east-index.js.map +1 -0
  63. package/dist/lib/east-project.d.ts +49 -0
  64. package/dist/lib/east-project.d.ts.map +1 -0
  65. package/dist/lib/east-project.js +121 -0
  66. package/dist/lib/east-project.js.map +1 -0
  67. package/dist/lib/east-py-lint.d.ts +7 -0
  68. package/dist/lib/east-py-lint.d.ts.map +1 -0
  69. package/dist/lib/east-py-lint.js +22 -0
  70. package/dist/lib/east-py-lint.js.map +1 -0
  71. package/dist/lib/east-rules-context.d.ts +7 -0
  72. package/dist/lib/east-rules-context.d.ts.map +1 -0
  73. package/dist/lib/east-rules-context.js +91 -0
  74. package/dist/lib/east-rules-context.js.map +1 -0
  75. package/dist/lib/hook-io.d.ts +30 -0
  76. package/dist/lib/hook-io.d.ts.map +1 -0
  77. package/dist/lib/hook-io.js +29 -0
  78. package/dist/lib/hook-io.js.map +1 -0
  79. package/dist/lib/host.d.ts +3 -0
  80. package/dist/lib/host.d.ts.map +1 -0
  81. package/dist/lib/host.js +9 -0
  82. package/dist/lib/host.js.map +1 -0
  83. package/dist/lib/lsp-client.d.ts +34 -0
  84. package/dist/lib/lsp-client.d.ts.map +1 -0
  85. package/dist/lib/lsp-client.js +200 -0
  86. package/dist/lib/lsp-client.js.map +1 -0
  87. package/dist/lib/plugin-status.d.ts +9 -0
  88. package/dist/lib/plugin-status.d.ts.map +1 -0
  89. package/dist/lib/plugin-status.js +164 -0
  90. package/dist/lib/plugin-status.js.map +1 -0
  91. package/dist/lib/review.d.ts +16 -0
  92. package/dist/lib/review.d.ts.map +1 -0
  93. package/dist/lib/review.js +74 -0
  94. package/dist/lib/review.js.map +1 -0
  95. package/dist/lib/search-guidance.d.ts +44 -0
  96. package/dist/lib/search-guidance.d.ts.map +1 -0
  97. package/dist/lib/search-guidance.js +73 -0
  98. package/dist/lib/search-guidance.js.map +1 -0
  99. package/dist/lib/search-hooks.d.ts +75 -0
  100. package/dist/lib/search-hooks.d.ts.map +1 -0
  101. package/dist/lib/search-hooks.js +117 -0
  102. package/dist/lib/search-hooks.js.map +1 -0
  103. package/dist/lib/search.d.ts +121 -0
  104. package/dist/lib/search.d.ts.map +1 -0
  105. package/dist/lib/search.js +181 -0
  106. package/dist/lib/search.js.map +1 -0
  107. package/dist/lib/transcript.d.ts +29 -0
  108. package/dist/lib/transcript.d.ts.map +1 -0
  109. package/dist/lib/transcript.js +109 -0
  110. package/dist/lib/transcript.js.map +1 -0
  111. package/dist/mcp/example-tools.d.ts +33 -0
  112. package/dist/mcp/example-tools.d.ts.map +1 -0
  113. package/dist/mcp/example-tools.js +97 -0
  114. package/dist/mcp/example-tools.js.map +1 -0
  115. package/dist/mcp/server.d.ts +2 -0
  116. package/dist/mcp/server.d.ts.map +1 -0
  117. package/dist/mcp/server.js +52 -0
  118. package/dist/mcp/server.js.map +1 -0
  119. package/dist/scripts/examples-file.d.ts +38 -0
  120. package/dist/scripts/examples-file.d.ts.map +1 -0
  121. package/dist/scripts/examples-file.js +224 -0
  122. package/dist/scripts/examples-file.js.map +1 -0
  123. package/dist/scripts/generate-index.d.ts +127 -0
  124. package/dist/scripts/generate-index.d.ts.map +1 -0
  125. package/dist/scripts/generate-index.js +315 -0
  126. package/dist/scripts/generate-index.js.map +1 -0
  127. package/dist/status/report.d.ts +2 -0
  128. package/dist/status/report.d.ts.map +1 -0
  129. package/dist/status/report.js +9 -0
  130. package/dist/status/report.js.map +1 -0
  131. package/index.json +44926 -0
  132. package/package.json +65 -3
  133. package/scripts/render-python.py +67 -0
package/README.md CHANGED
@@ -1,3 +1,105 @@
1
- # Temporary Holding Version
1
+ # Shared East Agent Plugin Library
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The common implementation used by [`east-claude-plugin`](../east-claude-plugin) and [`east-codex-plugin`](../east-codex-plugin), published as `@elaraai/east-plugin` for a Claude Code or Codex plugin that builds on East's (see [Building on East's plugin](#building-on-easts-plugin)). It is not an agent plugin itself.
4
+
5
+ - `lib/`: project detection, search, review and deduplication, daemon client, Python diagnostics, status, transcript helpers, the stdio LSP client, the search-first guidance and hooks (Claude Code's in `search-hooks.ts`, Codex's in `codex-hook.ts`), and the Codex skill adapter (`codex-skills.ts`).
6
+ - `hooks/`: common lifecycle and Claude tool adapters. Codex's one tool hook is `east-codex-plugin/hooks/codex-tools.ts`, over `lib/codex-hook.ts` and `lib/codex-tools.ts` (patch and shell normalization).
7
+ - `daemon/`: TypeScript/TSX LSP, Python LSP launcher and resident diagnostics server.
8
+ - `mcp/`: example search, retrieval, status and the real LSP bridge; the search and retrieval tools are `example-tools.ts`'s, for any server.
9
+ - `skills/`: the four plugin-native skills plus symlinks to package-owned skills.
10
+ - `scripts/`: the sole index generator, Python renderer, asset sync and CLI installation/update implementations.
11
+ - `index.json`: canonical generated corpus. Both plugins ship generated copies for independent installation, and the published package ships it for a plugin that serves East's corpus beside its own.
12
+
13
+ Host runtime files are thin package re-exports, inlined by esbuild. They must not grow independent copies of shared behavior. `hostText` translates host-facing tool and skill names at the output boundary. Generated host bundles and indexes intentionally contain duplicated bytes so installed plugins do not depend on this workspace package.
14
+
15
+ ```bash
16
+ pnpm --filter @elaraai/east-plugin run build
17
+ pnpm --filter @elaraai/east-plugin run generate-index
18
+ pnpm --filter @elaraai/east-claude-plugin run bundle
19
+ pnpm --filter @elaraai/east-codex-plugin run bundle
20
+ ```
21
+
22
+ Index generation requires built examples and the Python environment, as documented in either host README. After runtime changes, build this package before bundling hosts. Codex `build` and Claude `build` do that automatically; a root recursive build follows the workspace dependency graph.
23
+
24
+ Edit the skill's owning library, or its native source here. Claude skill links resolve here or directly to their owning packages. Codex materializes and adapts the same sources at bundle time. Run both host test suites after changes to shared behavior.
25
+
26
+ ## Building on East's plugin
27
+
28
+ A plugin for a product built on East — its own skills and its own examples — runs the same machinery over its own corpus, each piece called with the plugin's own names: the index generator, the example search and its MCP tools, the search-first hooks and the Codex skill adapter. East's own plugins are these, called with East's names.
29
+
30
+ ```bash
31
+ npm install @elaraai/east-plugin@<the East packages' version>
32
+ ```
33
+
34
+ **Versions.** `@elaraai/east-plugin` ships on the one version every `@elaraai/*` package shares, and depends on `@elaraai/east` and `@elaraai/east-diagnostics` at that version. Pin it exactly to the version of the East packages your examples use, and move them together. The East index it ships is that version's corpus.
35
+
36
+ ### The index
37
+
38
+ An `index.config.json` names the packages whose `*.examples.ts(x)` are indexed, as East's [`index.config.json`](./index.config.json) does: a source with `ir: true` is a package whose examples are East programs, read from its built test modules (`dist`) and stored as IR with the TypeScript printed from it; any other is stored as its authored source. Hand-written entries (a CLI's, say) go in an `index.static.json` of `{ "entries": [...] }`.
39
+
40
+ ```bash
41
+ npx east-plugin-generate-index --base-dir . --config index.config.json --static index.static.json --out index.json
42
+ # the python printings of the programs, optional (without them a program answers in TypeScript only):
43
+ uv run --with elaraai-east-py python node_modules/@elaraai/east-plugin/scripts/render-python.py index.json
44
+ ```
45
+
46
+ From code, `generateIndex({ baseDir, config, staticEntries })` (`@elaraai/east-plugin/scripts/generate-index`) returns the index to write.
47
+
48
+ ### The search
49
+
50
+ The search and fetch tools register on any MCP server with `registerExampleTools(server, { index, searchTool, getTool, corpus, packages, scopes })`, which answers once the index has loaded: a server awaits it before it connects, so an index that does not load fails it at startup, naming why. `scopes` are the npm scopes the corpus's packages are installed under, `@elaraai` unless given: the package filter accepts a name under any of them, and the search tool tells the agent not to read the installed packages there. A plugin composes its corpus with East's one of two ways:
51
+
52
+ - **One server answering both corpora.** Serve East's index beside your own: the package filter knows both, and an example id names its package, so the two never collide.
53
+
54
+ ```ts
55
+ import { fileURLToPath } from "node:url";
56
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
57
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
58
+ import { registerExampleTools } from "@elaraai/east-plugin/mcp/example-tools";
59
+ import { EAST_INDEX_PATH } from "@elaraai/east-plugin/lib/east-index";
60
+
61
+ const server = new McpServer({ name: "acme", version: "1.0.0" });
62
+ await registerExampleTools(server, {
63
+ index: [EAST_INDEX_PATH, fileURLToPath(new URL("../index.json", import.meta.url))],
64
+ searchTool: "search_acme_examples",
65
+ getTool: "get_acme_example",
66
+ corpus: "Acme",
67
+ packages: ["acme-cloud", "east", "e3"],
68
+ });
69
+ await server.connect(new StdioServerTransport());
70
+ ```
71
+
72
+ - **One each.** The East plugin, installed beside yours, answers East's corpus with its own tools; your server serves your index alone.
73
+
74
+ `EAST_INDEX_PATH` names the installed package's `index.json`. A bundled server, as East's plugins bundle theirs, copies that file into the plugin at build time and reads it from there.
75
+
76
+ ### The hooks
77
+
78
+ Claude Code runs a hook per event. Each is the shared function, configured with your names:
79
+
80
+ ```ts
81
+ // hooks/pre-write.ts — the gate on a write of your code with no search on record
82
+ import { gateText, type SearchGuidance } from "@elaraai/east-plugin/lib/search-guidance";
83
+ import { runPreWriteGate } from "@elaraai/east-plugin/lib/search-hooks";
84
+ import { SEARCH_TOOLS } from "@elaraai/east-plugin/lib/transcript";
85
+
86
+ const ACME: SearchGuidance = {
87
+ searchTool: "mcp__plugin_acme_acme__search_acme_examples",
88
+ getTool: "mcp__plugin_acme_acme__get_acme_example",
89
+ corpus: "Acme",
90
+ scope: "@elaraai",
91
+ };
92
+ await runPreWriteGate({
93
+ searchTools: [ACME.searchTool, ACME.getTool, ...SEARCH_TOOLS], // a search of East's corpus counts too
94
+ gateText: gateText(ACME),
95
+ isCorpusCode: (code) => /@elaraai\/acme/.test(code),
96
+ markerPrefix: "acme-search-seen",
97
+ requireSearchEnv: "ACME_REQUIRE_SEARCH",
98
+ });
99
+ ```
100
+
101
+ The read reminder is `runPreReadReminder({ readText: readText(ACME), isCorpusRead: corpusReadFor(ACME.scope) })`. Codex runs one hook over every tool: `codexSearchGate(event, options)` (`lib/codex-hook.ts`) records a search by your tools after the call, and before it reminds a read of your corpus and gates a write of your code; `writeSearchHookReply` writes its answer.
102
+
103
+ ### Skills
104
+
105
+ A plugin ships its own skills; East's come from the East plugin installed beside it. Codex caches only the plugin itself, so a Codex plugin ships its skills as real files, adapted from the Claude Code ones: `materializeSkills(sources, target, names)` (`lib/codex-skills.ts`) adapts each skill's Markdown — your tools lose Claude Code's MCP prefix, `/acme:` becomes `$acme-codex-plugin:`, the words speak of Codex — and copies every other file.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=east-py-lsp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"east-py-lsp.d.ts","sourceRoot":"","sources":["../../daemon/east-py-lsp.ts"],"names":[],"mappings":""}
@@ -0,0 +1,9 @@
1
+ import { runEastPyLsp } from "@elaraai/east-diagnostics";
2
+ // Plugin LSP server entry for python (#681): Claude Code launches this over
3
+ // stdio for `.py` files (declared in .claude-plugin/plugin.json, beside the
4
+ // TypeScript server). It is a launcher: in an east-py project it runs the
5
+ // project's own `east-py lsp` — the warm two-tier server — and falls back to a
6
+ // cold `east-py lint` per change when that cannot start; anywhere else it is a
7
+ // silent null server that never claims a file is clean.
8
+ runEastPyLsp();
9
+ //# sourceMappingURL=east-py-lsp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"east-py-lsp.js","sourceRoot":"","sources":["../../daemon/east-py-lsp.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAEzD,4EAA4E;AAC5E,4EAA4E;AAC5E,0EAA0E;AAC1E,+EAA+E;AAC/E,+EAA+E;AAC/E,wDAAwD;AACxD,YAAY,EAAE,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=lsp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lsp.d.ts","sourceRoot":"","sources":["../../daemon/lsp.ts"],"names":[],"mappings":""}
@@ -0,0 +1,7 @@
1
+ import { runEastLsp } from "@elaraai/east-diagnostics";
2
+ // Plugin LSP server entry: Claude Code launches this over stdio (declared in
3
+ // .claude-plugin/plugin.json) and injects the published diagnostics into the
4
+ // agent's context after every edit — native type errors rewritten as East
5
+ // type diffs, plus the east-diagnostics rule set.
6
+ runEastLsp();
7
+ //# sourceMappingURL=lsp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lsp.js","sourceRoot":"","sources":["../../daemon/lsp.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAEvD,6EAA6E;AAC7E,6EAA6E;AAC7E,0EAA0E;AAC1E,kDAAkD;AAClD,UAAU,EAAE,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../daemon/server.ts"],"names":[],"mappings":""}
@@ -0,0 +1,71 @@
1
+ import { createServer } from "node:net";
2
+ import { existsSync, unlinkSync } from "node:fs";
3
+ import { createDiagnosticsService } from "@elaraai/east-diagnostics";
4
+ // Resident East-diagnostics daemon: holds a warm LanguageService (via
5
+ // createDiagnosticsService) and answers per-file diagnose requests over a Unix
6
+ // socket. Spawned lazily by the PostToolUse client; exits after idle.
7
+ const socketPath = process.env["EAST_DIAG_SOCKET"];
8
+ if (socketPath === undefined)
9
+ process.exit(1);
10
+ const service = createDiagnosticsService();
11
+ const IDLE_MS = 10 * 60 * 1000;
12
+ let idleTimer;
13
+ function armIdle() {
14
+ if (idleTimer !== undefined)
15
+ clearTimeout(idleTimer);
16
+ idleTimer = setTimeout(() => {
17
+ server.close();
18
+ process.exit(0);
19
+ }, IDLE_MS);
20
+ idleTimer.unref();
21
+ }
22
+ const server = createServer((conn) => {
23
+ armIdle();
24
+ let buffer = "";
25
+ conn.on("data", (chunk) => {
26
+ buffer += chunk.toString("utf8");
27
+ const newline = buffer.indexOf("\n");
28
+ if (newline < 0)
29
+ return;
30
+ let response;
31
+ try {
32
+ const request = JSON.parse(buffer.slice(0, newline));
33
+ const text = typeof request.file === "string" ? service.diagnoseText(request.file) : "";
34
+ response = JSON.stringify({ ok: true, text });
35
+ }
36
+ catch (error) {
37
+ response = JSON.stringify({ ok: false, error: String(error) });
38
+ }
39
+ conn.end(`${response}\n`);
40
+ });
41
+ conn.on("error", () => undefined);
42
+ });
43
+ if (existsSync(socketPath)) {
44
+ try {
45
+ unlinkSync(socketPath);
46
+ }
47
+ catch {
48
+ /* ignore */
49
+ }
50
+ }
51
+ server.on("error", () => process.exit(1));
52
+ server.listen(socketPath, () => {
53
+ armIdle();
54
+ // Pre-build the LanguageService for this workspace now — off the edit
55
+ // critical path — so the first PostToolUse diagnose isn't a cold program load
56
+ // that blows the client's budget and silently drops the review. The daemon is
57
+ // spawned detached at SessionStart, so blocking here is fine; setImmediate
58
+ // lets `listen` settle first.
59
+ const cwd = process.env["EAST_DIAG_CWD"];
60
+ if (cwd !== undefined && cwd !== "") {
61
+ setImmediate(() => {
62
+ try {
63
+ service.warm(cwd);
64
+ }
65
+ catch {
66
+ /* best-effort warm */
67
+ }
68
+ });
69
+ }
70
+ });
71
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../daemon/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=diagnose-bash.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diagnose-bash.d.ts","sourceRoot":"","sources":["../../hooks/diagnose-bash.ts"],"names":[],"mappings":""}
@@ -0,0 +1,28 @@
1
+ import { readHookInput, writeHookOutput } from "../lib/hook-io.js";
2
+ import { reviewFile, reviewable } from "../lib/review.js";
3
+ import { writtenPaths } from "../lib/bash-writes.js";
4
+ // PostToolUse(Bash): review East files the command WROTE (#684).
5
+ //
6
+ // Until this hook existed, a file created through the shell — a `cat > f.py
7
+ // <<'EOF'` heredoc, a `>` redirect, `tee`, `sed -i` — passed through no gate
8
+ // and got no review, because every other hook matches Edit|Write or Read.
9
+ // Silent unless the command wrote East source; deduped by content per session
10
+ // through the same marker as the file hooks, so a file written here and read
11
+ // afterwards is reviewed once, not twice. The reviews run together: each has
12
+ // its own budget, and the hook has one of its own to fit inside.
13
+ async function main() {
14
+ const event = await readHookInput();
15
+ const command = event.tool_input?.command;
16
+ if (typeof command !== "string" || command === "")
17
+ process.exit(0);
18
+ const candidates = writtenPaths(command, event.cwd || process.cwd()).filter(reviewable);
19
+ if (candidates.length === 0)
20
+ process.exit(0);
21
+ const reviews = await Promise.all(candidates.slice(0, 10).map(async (path) => ({ path, text: await reviewFile(event.session_id, path) })));
22
+ const blocks = reviews.filter((r) => r.text !== null && r.text !== "").map((r) => `### ${r.path}\n${r.text}`);
23
+ if (blocks.length === 0)
24
+ process.exit(0);
25
+ writeHookOutput("PostToolUse", blocks.join("\n\n"));
26
+ }
27
+ main().catch(() => process.exit(0));
28
+ //# sourceMappingURL=diagnose-bash.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diagnose-bash.js","sourceRoot":"","sources":["../../hooks/diagnose-bash.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACnE,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAErD,iEAAiE;AACjE,EAAE;AACF,4EAA4E;AAC5E,6EAA6E;AAC7E,0EAA0E;AAC1E,8EAA8E;AAC9E,6EAA6E;AAC7E,6EAA6E;AAC7E,iEAAiE;AACjE,KAAK,UAAU,IAAI;IACjB,MAAM,KAAK,GAAG,MAAM,aAAa,EAAE,CAAC;IACpC,MAAM,OAAO,GAAG,KAAK,CAAC,UAAU,EAAE,OAAO,CAAC;IAC1C,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,EAAE;QAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAEnE,MAAM,UAAU,GAAG,YAAY,CAAC,OAAO,EAAE,KAAK,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IACxF,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAE7C,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC,KAAK,CAAC,UAAU,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC3I,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,IAAI,CAAC,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IAC9G,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACzC,eAAe,CAAC,aAAa,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;AACtD,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=diagnose.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diagnose.d.ts","sourceRoot":"","sources":["../../hooks/diagnose.ts"],"names":[],"mappings":""}
@@ -0,0 +1,33 @@
1
+ import { readHookInput, writeHookOutput } from "../lib/hook-io.js";
2
+ import { reviewFile } from "../lib/review.js";
3
+ // PostToolUse(Read|Edit|Write): preemptive East diagnostics.
4
+ //
5
+ // A TS/JS file that imports @elaraai/east goes through the warm daemon (native
6
+ // type errors + the east-diagnostics rule set); a python file that imports east
7
+ // goes through the project's own `east-py lint` (#648).
8
+ //
9
+ // On Read, both languages. On Edit/Write, PYTHON ONLY (#684): the agent's own
10
+ // TypeScript edits are covered by the plugin's language server, which Claude
11
+ // Code injects after every edit, but the python path through that server
12
+ // depends on `east-py` resolving from the file's directory and degrades to
13
+ // silence when it does not — which is what "the python hooks don't fire" was.
14
+ // Reviewing python here too costs nothing when the server already answered,
15
+ // because both share the per-session content-hash marker.
16
+ //
17
+ // Silent unless the file is East source in an East project and the reviewer
18
+ // answers within budget. All of the gating, resolution and dedupe lives in
19
+ // lib/review.ts, shared with the Bash hook.
20
+ async function main() {
21
+ const event = await readHookInput();
22
+ const filePath = event.tool_input?.file_path;
23
+ if (filePath === undefined)
24
+ process.exit(0);
25
+ if (event.tool_name !== "Read" && !filePath.endsWith(".py"))
26
+ process.exit(0);
27
+ const text = await reviewFile(event.session_id, filePath);
28
+ if (text === null || text === "")
29
+ process.exit(0);
30
+ writeHookOutput("PostToolUse", text);
31
+ }
32
+ main().catch(() => process.exit(0));
33
+ //# sourceMappingURL=diagnose.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diagnose.js","sourceRoot":"","sources":["../../hooks/diagnose.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACnE,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C,6DAA6D;AAC7D,EAAE;AACF,+EAA+E;AAC/E,gFAAgF;AAChF,wDAAwD;AACxD,EAAE;AACF,8EAA8E;AAC9E,6EAA6E;AAC7E,yEAAyE;AACzE,2EAA2E;AAC3E,8EAA8E;AAC9E,4EAA4E;AAC5E,0DAA0D;AAC1D,EAAE;AACF,4EAA4E;AAC5E,2EAA2E;AAC3E,4CAA4C;AAC5C,KAAK,UAAU,IAAI;IACjB,MAAM,KAAK,GAAG,MAAM,aAAa,EAAE,CAAC;IACpC,MAAM,QAAQ,GAAG,KAAK,CAAC,UAAU,EAAE,SAAS,CAAC;IAC7C,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC5C,IAAI,KAAK,CAAC,SAAS,KAAK,MAAM,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAE7E,MAAM,IAAI,GAAG,MAAM,UAAU,CAAC,KAAK,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;IAC1D,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,EAAE;QAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClD,eAAe,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC;AACvC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { READ_TEXT } from "../lib/search-guidance.js";
2
+ //# sourceMappingURL=pre-read.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pre-read.d.ts","sourceRoot":"","sources":["../../hooks/pre-read.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC"}
@@ -0,0 +1,11 @@
1
+ import { EAST_READ_REMINDER, runPreReadReminder } from "../lib/search-hooks.js";
2
+ // PreToolUse(Read|Grep|Glob): a reminder, never a refusal (#654). Reading the
3
+ // East packages' type declarations or sweeping the `*.examples.ts` corpus is
4
+ // the expensive way to learn the API the example index already holds, exact
5
+ // and printed in either language — so a read under node_modules/@elaraai, or
6
+ // a search over the example files, gets pointed at the search tool. A read of
7
+ // one specific project file is never touched. The reminder is
8
+ // lib/search-hooks.ts's, configured with East's names.
9
+ export { READ_TEXT } from "../lib/search-guidance.js";
10
+ runPreReadReminder(EAST_READ_REMINDER).catch(() => process.exit(0));
11
+ //# sourceMappingURL=pre-read.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pre-read.js","sourceRoot":"","sources":["../../hooks/pre-read.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAEhF,8EAA8E;AAC9E,6EAA6E;AAC7E,4EAA4E;AAC5E,6EAA6E;AAC7E,8EAA8E;AAC9E,8DAA8D;AAC9D,uDAAuD;AAEvD,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAEtD,kBAAkB,CAAC,kBAAkB,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { GATE_TEXT } from "../lib/search-guidance.js";
2
+ //# sourceMappingURL=pre-write.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pre-write.d.ts","sourceRoot":"","sources":["../../hooks/pre-write.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC"}
@@ -0,0 +1,11 @@
1
+ import { EAST_SEARCH_GATE, runPreWriteGate } from "../lib/search-hooks.js";
2
+ // PreToolUse(Edit|Write): the search-before-coding gate (#654). Nothing is
3
+ // injected any more — the agent pulls examples through the MCP search tool —
4
+ // so the first write of East code in a session with no search on record gets
5
+ // the instruction to search first. A reminder by default; `EAST_REQUIRE_SEARCH=deny`
6
+ // makes it a refusal of that write (the reason tells the agent what to do).
7
+ // Once a search is seen, a per-session marker keeps every later write silent.
8
+ // The gate is lib/search-hooks.ts's, configured with East's names.
9
+ export { GATE_TEXT } from "../lib/search-guidance.js";
10
+ runPreWriteGate(EAST_SEARCH_GATE).catch(() => process.exit(0));
11
+ //# sourceMappingURL=pre-write.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pre-write.js","sourceRoot":"","sources":["../../hooks/pre-write.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAE3E,2EAA2E;AAC3E,6EAA6E;AAC7E,6EAA6E;AAC7E,qFAAqF;AACrF,4EAA4E;AAC5E,8EAA8E;AAC9E,mEAAmE;AAEnE,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAEtD,eAAe,CAAC,gBAAgB,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=session-start.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-start.d.ts","sourceRoot":"","sources":["../../hooks/session-start.ts"],"names":[],"mappings":""}
@@ -0,0 +1,34 @@
1
+ import { readHookInput, writeHookOutput } from "../lib/hook-io.js";
2
+ import { getEastProjectInfo } from "../lib/east-project.js";
3
+ import { warmDaemon } from "../lib/diagnostics-client.js";
4
+ import { eastRulesContextFor } from "../lib/east-rules-context.js";
5
+ async function main() {
6
+ const event = await readHookInput();
7
+ const cwd = event.cwd || process.cwd();
8
+ const { isEast, skills, languages } = await getEastProjectInfo(cwd);
9
+ if (!isEast)
10
+ process.exit(0);
11
+ // Start the diagnostics daemon early so the first edit isn't cold.
12
+ warmDaemon(cwd);
13
+ const skillList = skills.map((s) => `/east:${s}`).join(", ");
14
+ const context = [
15
+ "This is an East project. East is a statically typed, expression-based language embedded in TypeScript — its patterns differ from regular TypeScript, so don't assume TS idioms carry over.",
16
+ "",
17
+ "East + e3 solutions are decision-oriented: they exist to improve a business decision and show the evidence behind it (\"decisions, not dashboards\"). The platform is a stack — an economic ontology (the typed model of the business) at the hub, an Integrate / Reason / Compute engine beneath it, and UI / agent / API surfaces above. Design top-down from the decision.",
18
+ "",
19
+ `Available East skills: ${skillList}. Invoke the relevant skill when writing East programs — they provide type-safe API patterns and examples. Each skill ends with a \"Related skills\" list; load those too when a task spans layers.`,
20
+ "Always available regardless of dependencies: /east:east-design (architect a solution before coding), /east:east-ontology (model the business as an economic ontology), /east:east-project (scaffold + run the build/deploy lifecycle).",
21
+ "",
22
+ "Finding East API usage (required):",
23
+ "- Before writing or changing East code, search the tested example index: call `mcp__plugin_east_east__search_east_examples` for each capability you are about to use (language: \"python\" for east-py, \"typescript\" otherwise) — summaries come back first — then `mcp__plugin_east_east__get_east_example` for the one that matches, and pattern your code on it. Nothing is injected for you; the search is the step, and every East skill requires it.",
24
+ "- Do NOT read `node_modules/@elaraai/**` or `*.examples.ts` files wholesale, and do not reason from `.d.ts` signatures: the index holds the same programs, exact and far cheaper, printed in either language from their IR. The signatures omit the runtime rules that make East code correct.",
25
+ "",
26
+ "Preemptive diagnostics:",
27
+ "- After you read or edit an East file, the plugin injects an `<east-code-review>` block listing TypeScript errors and East-specific idiom issues. Treat it as authoritative and fix what it flags — it's preemptive, so resolving it now avoids build-and-retry loops later. The rules it enforces are summarised below; write to them up front.",
28
+ "",
29
+ eastRulesContextFor(languages),
30
+ ].join("\n");
31
+ writeHookOutput("SessionStart", context);
32
+ }
33
+ main().catch(() => process.exit(0));
34
+ //# sourceMappingURL=session-start.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-start.js","sourceRoot":"","sources":["../../hooks/session-start.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAC1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AAEnE,KAAK,UAAU,IAAI;IACjB,MAAM,KAAK,GAAG,MAAM,aAAa,EAAE,CAAC;IACpC,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IAEvC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,kBAAkB,CAAC,GAAG,CAAC,CAAC;IACpE,IAAI,CAAC,MAAM;QAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAE7B,mEAAmE;IACnE,UAAU,CAAC,GAAG,CAAC,CAAC;IAEhB,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC7D,MAAM,OAAO,GAAG;QACd,4LAA4L;QAC5L,EAAE;QACF,+WAA+W;QAC/W,EAAE;QACF,0BAA0B,SAAS,qMAAqM;QACxO,wOAAwO;QACxO,EAAE;QACF,oCAAoC;QACpC,8bAA8b;QAC9b,gSAAgS;QAChS,EAAE;QACF,yBAAyB;QACzB,kVAAkV;QAClV,EAAE;QACF,mBAAmB,CAAC,SAAS,CAAC;KAC/B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAEb,eAAe,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC;AAC3C,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=subagent-start.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subagent-start.d.ts","sourceRoot":"","sources":["../../hooks/subagent-start.ts"],"names":[],"mappings":""}
@@ -0,0 +1,29 @@
1
+ import { readHookInput, writeHookOutput } from "../lib/hook-io.js";
2
+ import { getEastProjectInfo } from "../lib/east-project.js";
3
+ import { eastRulesContextFor } from "../lib/east-rules-context.js";
4
+ async function main() {
5
+ const event = await readHookInput();
6
+ const cwd = event.cwd || process.cwd();
7
+ const { isEast, skills, languages } = await getEastProjectInfo(cwd);
8
+ if (!isEast)
9
+ process.exit(0);
10
+ const skillList = skills.map((s) => `/east:${s}`).join(", ");
11
+ const packageList = skills
12
+ .map((s) => (s.startsWith("east-py") ? `elaraai-${s}` : `@elaraai/${s}`))
13
+ .join(", ");
14
+ const context = [
15
+ `This is an East project using ${packageList}.`,
16
+ "",
17
+ "When working with East code:",
18
+ "- REQUIRED before writing or modifying East code: call `mcp__plugin_east_east__search_east_examples` for each capability you are about to use (language: \"python\" for east-py, \"typescript\" otherwise), then `mcp__plugin_east_east__get_east_example` for the one that matches. Nothing is injected for you; the search is the step.",
19
+ "- Do NOT read `node_modules/@elaraai/**` or `*.examples.ts` files wholesale, and do not reason from `.d.ts` signatures — the index holds the same programs, exact and cheaper, printed in either language from their IR.",
20
+ "- After you read or edit an East file, the plugin injects an `<east-code-review>` block (TypeScript errors + East idiom issues). Treat it as authoritative and fix what it flags; the rules are summarised below so you can write to them up front.",
21
+ "- East is a statically typed, expression-based language embedded in TypeScript — it has unique patterns that differ from regular TypeScript.",
22
+ `- Available skills: ${skillList}`,
23
+ "",
24
+ eastRulesContextFor(languages),
25
+ ].join("\n");
26
+ writeHookOutput("SubagentStart", context);
27
+ }
28
+ main().catch(() => process.exit(0));
29
+ //# sourceMappingURL=subagent-start.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subagent-start.js","sourceRoot":"","sources":["../../hooks/subagent-start.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AAEnE,KAAK,UAAU,IAAI;IACjB,MAAM,KAAK,GAAG,MAAM,aAAa,EAAE,CAAC;IACpC,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IAEvC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,kBAAkB,CAAC,GAAG,CAAC,CAAC;IACpE,IAAI,CAAC,MAAM;QAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAE7B,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC7D,MAAM,WAAW,GAAG,MAAM;SACvB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;SACxE,IAAI,CAAC,IAAI,CAAC,CAAC;IAEd,MAAM,OAAO,GAAG;QACd,iCAAiC,WAAW,GAAG;QAC/C,EAAE;QACF,8BAA8B;QAC9B,2UAA2U;QAC3U,0NAA0N;QAC1N,qPAAqP;QACrP,8IAA8I;QAC9I,uBAAuB,SAAS,EAAE;QAClC,EAAE;QACF,mBAAmB,CAAC,SAAS,CAAC;KAC/B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAEb,eAAe,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC;AAC5C,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * The paths a bash command appears to write.
3
+ *
4
+ * Recognises `>` / `>>` redirects (which is also how a `cat > f <<'EOF'`
5
+ * heredoc lands), `tee`, `sed -i`, and `cp` / `mv` destinations. With `cwd`
6
+ * given, the paths come back absolute, and a `cd` earlier in the command
7
+ * moves the directory the segments after it resolve against — `cd src && cat
8
+ * > model.py` writes `src/model.py`, not `./model.py`.
9
+ */
10
+ export declare function writtenPaths(command: string, cwd?: string): string[];
11
+ //# sourceMappingURL=bash-writes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bash-writes.d.ts","sourceRoot":"","sources":["../../lib/bash-writes.ts"],"names":[],"mappings":"AAwCA;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAyBpE"}
@@ -0,0 +1,74 @@
1
+ import { resolve } from "node:path";
2
+ // Which files a bash command WRITES.
3
+ //
4
+ // Nothing in the plugin saw these: the hooks match Edit|Write and Read, so a
5
+ // file created by `cat > model.py <<'EOF'`, a `>` redirect, `tee`, or `sed -i`
6
+ // reached no gate and no review at all. That is not an edge case — it is a
7
+ // routine way files get written, and the dominant one when an agent is working
8
+ // through a shell.
9
+ //
10
+ // Over-matching is safe and under-matching is not: every candidate is checked
11
+ // for existence, extension and an East import before anything is said, so a
12
+ // path that turns out to be a device, a fd, or nothing at all costs one
13
+ // `readFile` that fails.
14
+ /** `2>&1`, `>&2`, `/dev/null` and friends — redirect targets that are not files. */
15
+ function isRealPath(token) {
16
+ if (token === "" || token.startsWith("&"))
17
+ return false;
18
+ if (/^\d+$/.test(token))
19
+ return false;
20
+ return !token.startsWith("/dev/");
21
+ }
22
+ function unquote(token) {
23
+ const m = /^(['"])(.*)\1$/.exec(token);
24
+ return m?.[2] ?? token;
25
+ }
26
+ /** Split a command into its pipeline/sequence segments, so `cp a b && cat > c`
27
+ * is read as two commands rather than one long argument list. */
28
+ function segments(command) {
29
+ return command.split(/\|\||&&|[;|\n]/);
30
+ }
31
+ /** The last path-like token of a segment — what `cp`, `mv` and `sed -i` write. */
32
+ function lastToken(segment) {
33
+ const tokens = segment.trim().split(/\s+/).map(unquote).filter((t) => t !== "" && !t.startsWith("-"));
34
+ const last = tokens.at(-1);
35
+ return last !== undefined && isRealPath(last) ? last : undefined;
36
+ }
37
+ /**
38
+ * The paths a bash command appears to write.
39
+ *
40
+ * Recognises `>` / `>>` redirects (which is also how a `cat > f <<'EOF'`
41
+ * heredoc lands), `tee`, `sed -i`, and `cp` / `mv` destinations. With `cwd`
42
+ * given, the paths come back absolute, and a `cd` earlier in the command
43
+ * moves the directory the segments after it resolve against — `cd src && cat
44
+ * > model.py` writes `src/model.py`, not `./model.py`.
45
+ */
46
+ export function writtenPaths(command, cwd) {
47
+ const found = new Set();
48
+ let dir = cwd;
49
+ const add = (path) => {
50
+ if (!isRealPath(path))
51
+ return;
52
+ found.add(dir === undefined ? path : resolve(dir, path));
53
+ };
54
+ for (const segment of segments(command)) {
55
+ const trimmed = segment.trim();
56
+ const moved = /^cd\s+(['"]?)([^\s'";|&<>]+)\1\s*$/.exec(trimmed);
57
+ if (moved !== null && dir !== undefined) {
58
+ dir = resolve(dir, unquote(moved[2] ?? ""));
59
+ continue;
60
+ }
61
+ for (const match of trimmed.matchAll(/>>?\s*(['"]?)([^\s'";|&<>]+)\1/g))
62
+ add(unquote(match[2] ?? ""));
63
+ for (const match of trimmed.matchAll(/\btee\s+(?:-a\s+)?(['"]?)([^\s'";|&<>]+)\1/g))
64
+ add(unquote(match[2] ?? ""));
65
+ // `sed -i … file`, `cp a b`, `mv a b`, `install … dest` — the destination is last
66
+ if (/^\s*(sed\s+(-[^\s]*\s+)*-i|cp|mv|install)\b/.test(trimmed)) {
67
+ const path = lastToken(trimmed);
68
+ if (path !== undefined)
69
+ add(path);
70
+ }
71
+ }
72
+ return [...found];
73
+ }
74
+ //# sourceMappingURL=bash-writes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bash-writes.js","sourceRoot":"","sources":["../../lib/bash-writes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,qCAAqC;AACrC,EAAE;AACF,6EAA6E;AAC7E,+EAA+E;AAC/E,2EAA2E;AAC3E,+EAA+E;AAC/E,mBAAmB;AACnB,EAAE;AACF,8EAA8E;AAC9E,4EAA4E;AAC5E,wEAAwE;AACxE,yBAAyB;AAEzB,oFAAoF;AACpF,SAAS,UAAU,CAAC,KAAa;IAC/B,IAAI,KAAK,KAAK,EAAE,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IACxD,IAAI,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACtC,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;AACpC,CAAC;AAED,SAAS,OAAO,CAAC,KAAa;IAC5B,MAAM,CAAC,GAAG,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACvC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC;AACzB,CAAC;AAED;iEACiE;AACjE,SAAS,QAAQ,CAAC,OAAe;IAC/B,OAAO,OAAO,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC;AACzC,CAAC;AAED,kFAAkF;AAClF,SAAS,SAAS,CAAC,OAAe;IAChC,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;IACtG,MAAM,IAAI,GAAG,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3B,OAAO,IAAI,KAAK,SAAS,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;AACnE,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,YAAY,CAAC,OAAe,EAAE,GAAY;IACxD,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAChC,IAAI,GAAG,GAAG,GAAG,CAAC;IACd,MAAM,GAAG,GAAG,CAAC,IAAY,EAAQ,EAAE;QACjC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,OAAO;QAC9B,KAAK,CAAC,GAAG,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC;IAC3D,CAAC,CAAC;IAEF,KAAK,MAAM,OAAO,IAAI,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QACxC,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;QAC/B,MAAM,KAAK,GAAG,oCAAoC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACjE,IAAI,KAAK,KAAK,IAAI,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;YACxC,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;YAC5C,SAAS;QACX,CAAC;QACD,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,QAAQ,CAAC,iCAAiC,CAAC;YAAE,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QACtG,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,QAAQ,CAAC,6CAA6C,CAAC;YAAE,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QAClH,kFAAkF;QAClF,IAAI,6CAA6C,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;YAChE,MAAM,IAAI,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC;YAChC,IAAI,IAAI,KAAK,SAAS;gBAAE,GAAG,CAAC,IAAI,CAAC,CAAC;QACpC,CAAC;IACH,CAAC;IAED,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC;AACpB,CAAC"}