jev-agent-tools 0.1.3 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/CHANGELOG.md +88 -3
  2. package/CONTRIBUTING.md +40 -0
  3. package/README.md +64 -9
  4. package/SECURITY.md +27 -0
  5. package/dist/adapters/analysis-context.js +75 -0
  6. package/dist/adapters/ask-files.js +189 -0
  7. package/dist/adapters/ask-proof.js +144 -0
  8. package/dist/adapters/ask-syntax.js +385 -0
  9. package/dist/adapters/canonical-path.js +17 -0
  10. package/dist/adapters/command.js +181 -0
  11. package/dist/adapters/docs.js +172 -0
  12. package/dist/adapters/exec.js +207 -0
  13. package/dist/adapters/files.js +293 -0
  14. package/dist/adapters/find.js +122 -0
  15. package/dist/adapters/git-base.js +26 -0
  16. package/dist/adapters/git-inventory.js +71 -0
  17. package/dist/adapters/git.js +439 -0
  18. package/dist/adapters/locate-file.js +159 -0
  19. package/dist/adapters/output-lines.js +46 -0
  20. package/dist/adapters/private-storage.js +98 -0
  21. package/dist/adapters/risk-callers.js +426 -0
  22. package/dist/adapters/runner-version.js +78 -0
  23. package/dist/adapters/shell.js +76 -0
  24. package/dist/adapters/syntax.js +187 -0
  25. package/dist/adapters/test-inventory.js +131 -0
  26. package/dist/adapters/usage.js +20 -0
  27. package/dist/adapters/utf8.js +47 -0
  28. package/dist/configuration.js +257 -0
  29. package/dist/constants.js +119 -0
  30. package/dist/core/ask-closure.js +282 -0
  31. package/dist/core/ask-proof.js +1 -0
  32. package/dist/core/ask-references.js +194 -0
  33. package/dist/core/asks.js +436 -0
  34. package/dist/core/batches.js +65 -0
  35. package/dist/core/command-output.js +224 -0
  36. package/dist/core/diff.js +178 -0
  37. package/dist/core/docs.js +302 -0
  38. package/dist/core/find.js +108 -0
  39. package/dist/core/git.js +1 -0
  40. package/dist/core/imports.js +550 -0
  41. package/dist/core/integrity.js +45 -0
  42. package/dist/core/lexical.js +132 -0
  43. package/dist/core/locate.js +169 -0
  44. package/dist/core/output.js +120 -0
  45. package/dist/core/pointer.js +29 -0
  46. package/dist/core/risk-callers.js +851 -0
  47. package/dist/core/runner-version.js +45 -0
  48. package/dist/core/sections.js +230 -0
  49. package/dist/core/state.js +44 -0
  50. package/dist/core/syntax.js +1 -0
  51. package/dist/core/test-commands.js +334 -0
  52. package/dist/core/test-coverage.js +74 -0
  53. package/dist/core/test-discovery.js +1382 -0
  54. package/dist/core/test-evidence.js +527 -0
  55. package/dist/core/test-state.js +81 -0
  56. package/dist/core/truncate.js +12 -0
  57. package/dist/core/units.js +349 -0
  58. package/dist/describe.js +23 -0
  59. package/dist/guide.js +33 -0
  60. package/dist/host.js +24 -0
  61. package/dist/jev/client.js +434 -0
  62. package/dist/jev/pool.js +54 -0
  63. package/dist/jev/types.js +1 -0
  64. package/dist/mcp/main.js +124 -0
  65. package/dist/mcp/protocol.js +187 -0
  66. package/dist/mcp/tools.js +116 -0
  67. package/dist/presets/docs.js +62 -0
  68. package/dist/presets/risk.js +179 -0
  69. package/dist/presets/spec.js +81 -0
  70. package/dist/presets/witnesses.js +249 -0
  71. package/dist/render.js +42 -0
  72. package/dist/result.js +3 -0
  73. package/dist/runtime.js +1 -0
  74. package/dist/session.js +147 -0
  75. package/dist/texts/ask-files.js +1 -0
  76. package/dist/texts/ask.js +2 -0
  77. package/dist/texts/check-diff.js +17 -0
  78. package/dist/texts/configuration.js +1 -0
  79. package/dist/texts/find.js +14 -0
  80. package/dist/texts/guide.js +16 -0
  81. package/dist/texts/locate.js +10 -0
  82. package/dist/texts/select-tests.js +2 -0
  83. package/dist/tools/ask-files.js +217 -0
  84. package/dist/tools/ask-schema.js +70 -0
  85. package/dist/tools/ask.js +686 -0
  86. package/dist/tools/check-diff.js +402 -0
  87. package/dist/tools/docs-check.js +299 -0
  88. package/dist/tools/find.js +389 -0
  89. package/dist/tools/locate.js +303 -0
  90. package/dist/tools/select-tests.js +567 -0
  91. package/dist/tools/spec-check.js +166 -0
  92. package/docs/adr/0001-strict-typescript-pure-core-offline-tests.md +31 -0
  93. package/docs/adr/0002-one-http-protocol-across-hosts.md +17 -0
  94. package/docs/adr/0003-explicit-scope-conservative-automation.md +19 -0
  95. package/docs/adr/0004-compiled-typed-intents.md +19 -0
  96. package/docs/adr/0005-evidence-construction-before-judgment.md +19 -0
  97. package/docs/adr/0006-visible-uncertainty-constrained-controls.md +21 -0
  98. package/docs/adr/0007-bounded-evidence-visible-limits.md +21 -0
  99. package/docs/adr/0008-static-test-discovery-conservative-plans.md +19 -0
  100. package/docs/adr/0009-session-cache-requested-model-identity.md +17 -0
  101. package/docs/adr/0010-mcp-server-thin-host.md +23 -0
  102. package/docs/agent-instructions.md +91 -0
  103. package/docs/design.md +3 -3
  104. package/docs/mcp.md +231 -0
  105. package/package.json +19 -4
  106. package/server.json +57 -0
  107. package/src/adapters/canonical-path.ts +18 -0
  108. package/src/adapters/command.ts +7 -4
  109. package/src/adapters/exec.ts +226 -0
  110. package/src/adapters/private-storage.ts +143 -0
  111. package/src/adapters/risk-callers.ts +4 -2
  112. package/src/adapters/shell.ts +97 -0
  113. package/src/configuration.ts +294 -0
  114. package/src/constants.ts +11 -0
  115. package/src/core/command-output.ts +17 -1
  116. package/src/host-tui.d.ts +14 -0
  117. package/src/host.ts +11 -0
  118. package/src/index.ts +13 -5
  119. package/src/jev/client.ts +12 -0
  120. package/src/jev/types.ts +6 -0
  121. package/src/mcp/main.ts +135 -0
  122. package/src/mcp/protocol.ts +282 -0
  123. package/src/mcp/tools.ts +166 -0
  124. package/src/secret-input.ts +222 -0
  125. package/src/session.ts +59 -0
  126. package/src/setup.ts +170 -0
  127. package/src/texts/configuration.ts +1 -1
  128. package/src/tools/ask-files.ts +8 -13
  129. package/src/tools/ask.ts +29 -28
  130. package/src/tools/check-diff.ts +11 -11
  131. package/src/tools/docs-check.ts +1 -0
  132. package/src/tools/find.ts +8 -8
  133. package/src/tools/locate.ts +8 -9
  134. package/src/tools/select-tests.ts +10 -10
  135. package/src/tools/spec-check.ts +1 -0
package/CHANGELOG.md CHANGED
@@ -9,10 +9,93 @@ modules are not a stable library API.
9
9
 
10
10
  ## [Unreleased]
11
11
 
12
+ ## [0.2.0] - 2026-10-03
13
+
14
+ ### Added
15
+
16
+ - `jev-agent-tools-mcp`: a dependency-free MCP stdio server exposing the same six
17
+ tools to any MCP client. It reuses the existing tool factories, session limits,
18
+ HTTP client and saved configuration; the binary ships as compiled JavaScript built
19
+ by `npm run build`.
20
+ - MCP setup guide (`docs/mcp.md`) and a project instruction template for MCP agents
21
+ (`docs/agent-instructions.md`, for `CLAUDE.md`, `AGENTS.md` or Kiro steering).
22
+ - `JEV_TOOLS_BASH` and `JEV_TOOLS_ROOT` settings (Windows bash path; MCP root).
23
+ - MCP Registry publication: `server.json` (`io.github.NomenAK/jev-agent-tools`) and
24
+ `mcpName` in `package.json`. After the npm publish, the release workflow waits for the
25
+ approved version and its `mcpName` on npm, then publishes with a pinned, checksum-verified
26
+ `mcp-publisher` over GitHub OIDC; an existing registry version is skipped only
27
+ when its server definition matches the approved tarball's metadata.
28
+ - `scripts/check-mcp-package.ts`: CI and release gate that installs the packed
29
+ tarball, checks modern discovery and all advertised legacy handshakes, and
30
+ exercises `jev_ask` against a local synthetic HTTP endpoint.
31
+ - Native Windows and macOS CI for MCP, managed process termination, private
32
+ configuration, platform paths and the installed package; Linux retains the
33
+ complete offline suite.
34
+
35
+ ### Changed
36
+
37
+ - The npm package now includes `SECURITY.md` and `docs/adr/`, which shipped
38
+ documentation already linked to.
39
+
40
+ ### Fixed
41
+
42
+ - MCP command cancellation and timeout now finish process-group escalation even
43
+ when the parent shell exits first. Closing stdin, SIGTERM and SIGINT drain
44
+ outstanding tool calls before the server exits; cancelled calls stay silent.
45
+ - Windows command cleanup maps MSYS descendants before forced termination, so
46
+ Git Bash fork/exec no longer hides ordinary descendants from `taskkill /T`.
47
+ - Package checks use local archive paths on Windows and macOS; private-storage
48
+ test fixtures use canonical macOS temporary paths without relaxing symlink checks.
49
+ - Modern MCP discovery now includes server identity in result metadata, and
50
+ tool lists declare immediately stale, private caching as required by the
51
+ 2026-07-28 protocol.
52
+ - MCP Registry reruns skip only an identical server definition. Divergent,
53
+ malformed or inconclusive responses fail; registry metadata is checked
54
+ against `server.json` inside the approved tarball before lookup.
55
+
56
+ - In omp, first-launch Jev setup no longer waits for credential entry inside the
57
+ bounded `session_start` handler. The offer and input dialogs stay open while
58
+ users find their endpoint and key, rather than closing after the host's
59
+ 30-second event deadline.
60
+ - `JEV_TOOLS_MAX_USD` could be overshot by up to seven requests: concurrent batches
61
+ were all admitted before any cost was reported. Under a USD limit, requests are
62
+ now admitted one at a time, so only the final admitted request can exceed it.
63
+ - Windows: `jev_ask` commands ran `env CI=1 bash`, which fails without `env` and
64
+ resolves to the WSL launcher. Git for Windows bash is now used.
65
+ - Windows: imported providers were never found for `jev_check_diff` risk callers
66
+ because a repository path was resolved with platform path semantics.
67
+ - Windows: node:test `file:///C:/...` failure locations (including `%20`) were not
68
+ mapped back to repository files.
69
+ - Windows: an 8.3 short working directory (for example `C:\Users\NAME~1`) did not
70
+ relate to the Git root, which emptied import closures and file references.
71
+ - Line endings are pinned to LF via `.gitattributes`, so lint and the rule/guideline
72
+ parity check pass on Windows checkouts with `core.autocrlf=true`.
73
+ - Windows: saving or loading `/jev-setup` configuration always failed, because the
74
+ privacy check read POSIX mode bits, which Windows reports as `0o666` for every file.
75
+ Windows now checks the folder and file access lists (allow entries limited to the
76
+ current user, SYSTEM and Administrators) and restricts a new folder when saving.
77
+ - Tests: `secret-input` passed a `C:\` path to `import()`; the risk-caller latency
78
+ test compared one run with a fixed 250 ms and now checks 250- to 1000-line scaling.
79
+
80
+ ## [0.1.4] - 2026-10-02
81
+
82
+ ### Added
83
+
84
+ - Interactive configuration for pi and omp: first-launch offer, `/jev-setup`, masked
85
+ key entry, `--jev-skip-setup`, `--jev-url` and `--jev-model`. Configuration applies
86
+ immediately; optional saving uses a private file outside the repository and stores the
87
+ key in plaintext. No dialog appears in print, JSON, RPC or sub-agent sessions.
88
+
89
+ ### Changed
90
+
91
+ - Release publication uses trusted publishing (OIDC) without a temporary npm token.
92
+ - Document the npm release's verified pi 1.0.0 integration, simulated-service smoke
93
+ scope and non-blocking `@sinclair/typebox` packaging warning.
94
+
12
95
  ## [0.1.3] - 2026-10-02
13
96
 
14
- Initial npm release candidate, under the name `jev-agent-tools`. Versions 0.1.0,
15
- 0.1.1 and 0.1.2 were tagged but never published; their outcomes are recorded below.
97
+ First npm release, published as `jev-agent-tools`. Versions 0.1.0, 0.1.1 and 0.1.2
98
+ were tagged but never published; their outcomes are recorded below.
16
99
 
17
100
  ### Added
18
101
 
@@ -59,7 +142,9 @@ Tagged but never published to npm: the unscoped package name was rejected.
59
142
 
60
143
  Tagged but never published to npm: the publish workflow failed before upload.
61
144
 
62
- [Unreleased]: https://github.com/NomenAK/jev-tools/compare/v0.1.3...HEAD
145
+ [Unreleased]: https://github.com/NomenAK/jev-tools/compare/v0.2.0...HEAD
146
+ [0.2.0]: https://github.com/NomenAK/jev-tools/compare/v0.1.4...v0.2.0
147
+ [0.1.4]: https://github.com/NomenAK/jev-tools/compare/v0.1.3...v0.1.4
63
148
  [0.1.3]: https://github.com/NomenAK/jev-tools/compare/v0.1.0...v0.1.3
64
149
  [0.1.2]: https://github.com/NomenAK/jev-tools/tree/v0.1.2
65
150
  [0.1.1]: https://github.com/NomenAK/jev-tools/tree/v0.1.1
@@ -0,0 +1,40 @@
1
+ # Contributing
2
+
3
+ Issues and external pull requests are welcome. Use English for issues, pull requests, documentation, comments, and test descriptions; keep non-English fixtures when they test language or encoding behavior. For vulnerabilities, follow [SECURITY.md](SECURITY.md).
4
+
5
+ ## Issues or pull requests?
6
+
7
+ Open an issue for a reproducible product bug or a feature request. Include the package and host versions, expected and actual behavior, and a minimal sanitized reproduction. Small, focused fixes or documentation corrections can go straight to a pull request. Discuss new tools, contract changes, and research-dependent features in an issue before implementing them.
8
+
9
+ An issue may describe an unmet need without claiming that a method already works. Features such as co-change suggestions are not accepted for implementation without a new, validated method. Propose the method, its evaluation protocol, and reproducible evidence in an issue first; an unvalidated heuristic or a renamed version of it is not enough.
10
+
11
+ ## Local checks
12
+
13
+ Use Node.js 24 or newer and npm. From a checkout, with development dependencies enabled:
14
+
15
+ ```sh
16
+ npm ci --include=optional
17
+ npm run typecheck
18
+ npm run build
19
+ npm run check:imports
20
+ npm run lint
21
+ npm test
22
+ ```
23
+
24
+ `npm run build` compiles only the MCP server into `dist/` (git-ignored); `npm pack` runs it automatically. To try the server against a checkout, see [From a clone](docs/mcp.md#from-a-clone). CI packs the package and runs `node scripts/check-mcp-package.ts <tarball>`: it installs the tarball into an empty directory, checks modern discovery and all advertised legacy handshakes, lists the six tools with their schemas and caching fields, and runs `jev_ask` through a local synthetic HTTP endpoint. This verifies installed integration, not live model accuracy. Linux runs the full offline suite; native Windows and macOS jobs run targeted MCP, process, configuration and packaging checks.
25
+
26
+ ## Releases
27
+
28
+ Maintainers release by pushing a `vX.Y.Z` tag on reviewed `main`. Before tagging, set the same version in `package.json`, in both `version` fields of `server.json`, and in a `CHANGELOG.md` section; `test/server-json.test.ts` fails if they drift. [`publish.yml`](.github/workflows/publish.yml) then packs once, checks the packed MCP server, publishes the approved tarball to npm with trusted publishing, publishes `server.json` to the MCP Registry with GitHub OIDC, and creates a draft GitHub Release. Each publication step skips a version that already exists with the same content and fails on anything inconclusive.
29
+
30
+ Keep optional native dependencies enabled for development. These checks make no Jev API calls and need no Jev key. Dependency installation may use the network. Some runner-identity tests skip when additional runners are unavailable locally; CI provisions those runners in [.github/workflows/ci.yml](.github/workflows/ci.yml).
31
+
32
+ Maintainers run a private regression probe on reviewed release candidates before release and on the published package before announcing it. Contributors do not need access to that probe or its credentials.
33
+
34
+ ## Pull request expectations
35
+
36
+ Keep changes focused. Explain the user-visible behavior, link the relevant issue when there is one, and report the checks you actually ran, including skips or limitations. Add or update deterministic tests for changed behavior and regressions, and update affected documentation. Public CI must pass without service credentials.
37
+
38
+ Do not make accuracy, recall, speed, or other measurement claims without describing the method, baseline, data, and limitations sufficiently to reproduce and review them. Do not include credentials, raw service responses, private data, or personal configuration in issues, patches, or logs. Use synthetic or sanitized examples.
39
+
40
+ Check the name and email attached to your commits before submitting; GitHub's noreply email is an option if you do not want to publish a personal email address.
package/README.md CHANGED
@@ -1,31 +1,82 @@
1
1
  # jev-agent-tools
2
2
 
3
- Six evidence-oriented tools for pi and omp, compatible with the Jev API format. Use them to navigate unfamiliar code, ask typed questions about repository evidence, review completed changes and select existing tests. They complement reading, searching and execution; they do not replace them.
3
+ Six evidence-oriented tools for pi, omp and any MCP client, compatible with the Jev API format. Use them to navigate unfamiliar code, ask typed questions about repository evidence, review completed changes and select existing tests. They complement reading, searching and execution; they do not replace them.
4
+
5
+ [![jev-agent-tools launch video: six tools, one habit, show the evidence. Click to play (74 s).](docs/media/jev-agent-tools-launch.jpg)](docs/media/jev-agent-tools-launch.mp4)
6
+
7
+ [Watch the launch video](docs/media/jev-agent-tools-launch.mp4) (74 s, MP4).
4
8
 
5
9
  ## Install
6
10
 
7
- Install through your host's package manager. The npm package is `jev-agent-tools`. It ships TypeScript sources; no separate compilation is required.
11
+ Install through your host's package manager, or register the MCP server with an MCP client. The npm package is `jev-agent-tools`. pi and omp load its TypeScript sources directly; the MCP server ships prebuilt.
8
12
 
9
13
  ### pi
10
14
 
11
15
  ```sh
12
- pi install npm:jev-agent-tools@0.1.3
16
+ pi install npm:jev-agent-tools@0.2.0
13
17
  # Project-local installation:
14
- pi install -l npm:jev-agent-tools@0.1.3
18
+ pi install -l npm:jev-agent-tools@0.2.0
15
19
  ```
16
20
 
17
21
  ### omp
18
22
 
19
23
  ```sh
20
- omp plugin install jev-agent-tools@0.1.3
24
+ omp plugin install jev-agent-tools@0.2.0
21
25
  ```
22
26
 
27
+ ### Any MCP client
28
+
29
+ Since version 0.2.0, the package also ships `jev-agent-tools-mcp`, a stdio MCP server that exposes the same six tools to any MCP client: Claude Code, Claude Desktop, Kiro, Cursor, VS Code, Codex CLI and others. A typical `mcpServers` entry:
30
+
31
+ ```json
32
+ {
33
+ "mcpServers": {
34
+ "jev": {
35
+ "command": "npx",
36
+ "args": ["-y", "-p", "jev-agent-tools", "jev-agent-tools-mcp", "--root", "/path/to/repository"],
37
+ "env": {
38
+ "JEV_TOOLS_URL": "${JEV_TOOLS_URL}",
39
+ "JEV_TOOLS_API_KEY": "${JEV_TOOLS_API_KEY}"
40
+ }
41
+ }
42
+ }
43
+ }
44
+ ```
45
+
46
+ On Windows most clients start commands without a shell, so use `"command": "cmd"` with `"/c", "npx"` at the start of `args`. The [MCP setup guide](docs/mcp.md) has per-client files, CLI commands, variable interpolation, verification and troubleshooting. Releases are also listed in the official MCP Registry as `io.github.NomenAK/jev-agent-tools`. Add the [agent instructions](docs/agent-instructions.md) to `CLAUDE.md`, `AGENTS.md` or a Kiro steering file so the agent uses and reads the tools correctly.
47
+
48
+ The server reads the same environment variables as pi and omp and the configuration saved by `/jev-setup`. One server process is one session. The automatic run-end documentation check does not exist in MCP; call `jev_check_diff` with `check: "docs"` instead. `jev_ask` is marked as not read-only while commands are enabled; set `JEV_TOOLS_ALLOW_COMMAND=0` to remove `command` from its schema.
49
+
23
50
  ### Requirements and compatibility
24
51
 
25
- Requires Node.js 24 or later. Supported host baselines are pi 0.87.1 and omp 18.4.10. These are support baselines, not claims that every later version has been individually validated. omp uses its Bun runtime; Node.js is also required for Node-based project checks. Git and, for optional command evidence, Bash must be available. Optional native parsers and file-search acceleration may be unavailable on some platforms; affected tools report their limitations.
52
+ Requires Node.js 24 or later. Supported host baselines are pi 0.87.1 and omp 18.4.10. These are support baselines, not claims that every later version has been individually validated. omp uses its Bun runtime; Node.js is also required for Node-based project checks. Git and, for optional command evidence, Bash must be available. On Windows, command evidence uses Git for Windows bash (found next to `git` on `PATH` or under Program Files); the WSL `bash.exe` launchers are never used. Set `JEV_TOOLS_BASH` to the full path of another bash. Optional native parsers and file-search acceleration may be unavailable on some platforms; affected tools report their limitations.
53
+
54
+ The published `jev-agent-tools@0.1.3` package was also checked with **pi 1.0.0** on Linux under Node.js 24.15.0: npm installation, TypeScript checking against the host types, all six tools in the actual CLI, and reading-guide injection passed. The CLI smoke used a simulated conversation provider and Jev endpoint; it verifies host integration, not live model accuracy or every tool scenario.
55
+
56
+ **pi 1.0.0 packaging warning:** the package declares `@sinclair/typebox` in `dependencies`, while pi expects host-provided extension modules in `peerDependencies` with a `"*"` range. pi warns that separately installed copies can create duplicate runtime modules. The warning did not prevent the smoke from completing; it remains a packaging issue, not a claim of warning-free compatibility.
26
57
 
27
58
  ## Configure
28
59
 
60
+ ### Interactive setup
61
+
62
+ In the main interactive terminal of pi or omp, a first launch without configuration offers **Configure now / Later**. Run `/jev-setup` at any time to change the endpoint, model or key. The key is typed in a masked field and is never passed as a command-line argument. Choose **This session only** (nothing is written) or **Save for future sessions**. Cancelling at any step changes nothing.
63
+
64
+ | Flag | Effect |
65
+ |---|---|
66
+ | `--jev-skip-setup` | Suppress the first-launch offer for this run; `/jev-setup` still works. |
67
+ | `--jev-url <url>` | Endpoint for this run. |
68
+ | `--jev-model <name>` | Jev model for this run, not the conversation model. |
69
+
70
+ No dialog appears in print, JSON or RPC modes, or in sub-agents; configure those with the environment variables below. Changes apply immediately, without restarting the host.
71
+
72
+ Interactive setup waits for submission or cancellation, without a credential-entry timeout. The first-launch offer does not keep the host's startup event handler open while you find the endpoint or key.
73
+
74
+ **Precedence per field:** environment variable, then launch flag, then this session's setup, then saved configuration, then the default model `openjev`. A field set by the environment or a flag is shown as controlled and is never saved.
75
+
76
+ **Saved configuration** lives outside the repository at `$XDG_CONFIG_HOME/jev-agent-tools/config.json` (default `~/.config/jev-agent-tools/config.json`), in a private directory (`0700`) with a private file (`0600`). On Windows, where mode bits do not exist, privacy is the folder's access list: saving restricts it to you, SYSTEM and Administrators, and loading refuses any other account with access. **The key is stored in plaintext, not encrypted.** Storage that is a symlink, accessible to other users, not owned by you or malformed is refused rather than overwritten. The MCP server also reads this file, after environment variables.
77
+
78
+ ### Environment variables
79
+
29
80
  Set these before starting the host, using your own endpoint and credentials:
30
81
 
31
82
  ```sh
@@ -35,7 +86,7 @@ export JEV_TOOLS_API_KEY="${YOUR_JEV_API_KEY}"
35
86
  export JEV_TOOLS_MODEL="openjev"
36
87
  ```
37
88
 
38
- `YOUR_*` placeholders are inputs you supply, not additional product settings. Configuration is read when the extension loads; restart the host after changing it.
89
+ `YOUR_*` placeholders are inputs you supply, not additional product settings. Environment variables are read when the extension loads and take precedence over everything else.
39
90
 
40
91
  | Variable | Meaning |
41
92
  |---|---|
@@ -46,6 +97,8 @@ export JEV_TOOLS_MODEL="openjev"
46
97
  | `JEV_TOOLS_MAX_USD` | Session-wide finite non-negative cost limit, including fractions; absent or empty means unlimited. Invalid values refuse requests. |
47
98
  | `JEV_TOOLS_ALLOW_COMMAND` | `0` disables `command` in `jev_ask`; otherwise commands run with ordinary shell permissions, without an additional sandbox. |
48
99
  | `JEV_TOOLS_AUTO_DOCS` | `0` disables the automatic run-end documentation check. |
100
+ | `JEV_TOOLS_BASH` | Optional full path of the bash used for `jev_ask` commands on Windows. |
101
+ | `JEV_TOOLS_ROOT` | MCP server only: repository directory when `--root` is not given. |
49
102
 
50
103
  Without the endpoint or key, tools remain registered and explain the missing configuration; the automatic documentation check is disabled. There is no fallback to a chat model. Per-tool `max_calls` is separate from session limits. A model name echoed by the response does not establish which model was actually served.
51
104
 
@@ -86,15 +139,17 @@ The extension supplies the shared reading guide in both hosts. omp discovers ena
86
139
 
87
140
  > Before concluding that a failure is a code bug, an incorrect test or an environment problem, or that a plan matches documentation, pass the relevant files to [jev_ask](docs/tools/jev_ask.md) and weigh its answer against your own reading. Include both the failing test and the code it exercises; identify any conclusion that remains unconfirmed.
88
141
 
142
+ MCP clients receive the guide as server `instructions`, which some clients ignore. Add the [agent instructions](docs/agent-instructions.md) to the project's `CLAUDE.md`, `AGENTS.md` or Kiro steering file.
143
+
89
144
  ## Data and command safety
90
145
 
91
146
  Repository evidence, notes and optional command output are sent to your configured endpoint. Review its data-handling policy before using confidential repositories. See [security guidance](SECURITY.md).
92
147
 
93
- File collection is confined to the repository: absolute paths, parent traversal, escaping symlinks, Git metadata and internal URLs are not file inputs. Build output, binaries, lockfiles and oversized files are skipped or refused with visible limits; evidence is not silently truncated into a verdict. This confinement does **not** sandbox a command. `jev_ask` commands can read, write or access the network with the host's shell permissions. omp uses execution approval for commands; pi does not supply an additional per-tool command approval. Set `JEV_TOOLS_ALLOW_COMMAND=0` to disable them.
148
+ File collection is confined to the repository: absolute paths, parent traversal, escaping symlinks, Git metadata and internal URLs are not file inputs. Build output, binaries, lockfiles and oversized files are skipped or refused with visible limits; evidence is not silently truncated into a verdict. This confinement does **not** sandbox a command. `jev_ask` commands can read, write or access the network with the host's shell permissions. omp uses execution approval for commands; pi does not supply an additional per-tool command approval; MCP clients apply their own tool approval, and the server marks `jev_ask` as not read-only. Set `JEV_TOOLS_ALLOW_COMMAND=0` to disable them.
94
149
 
95
150
  ## Automatic documentation check
96
151
 
97
- On a dirty tree, the extension can check existing Markdown documentation once at run end against changes from `HEAD`, including untracked files. A flagged existing sentence can request one additional turn to update it or explain why it remains correct. Merely unsure sections do not trigger another turn. Missing configuration, disabled automation, invalid/exhausted session budgets or a clean tree skip the check. Errors and timeout do not block the host. This is not a check for every missing documentation obligation.
152
+ On a dirty tree, the extension can check existing Markdown documentation once at run end against changes from `HEAD`, including untracked files. A flagged existing sentence can request one additional turn to update it or explain why it remains correct. Merely unsure sections do not trigger another turn. Missing configuration, disabled automation, invalid/exhausted session budgets or a clean tree skip the check. Errors and timeout do not block the host. This is not a check for every missing documentation obligation. The MCP server has no run-end hook; there, call `jev_check_diff` with `check: "docs"` before finishing.
98
153
 
99
154
  ## Known limits
100
155
 
package/SECURITY.md ADDED
@@ -0,0 +1,27 @@
1
+ # Security policy
2
+
3
+ ## Supported versions
4
+
5
+ Security fixes target the latest patch release in the 0.1.x series. Older 0.1.x patches should be upgraded; 0.0.x and unreleased snapshots are not supported releases.
6
+
7
+ ## Report privately
8
+
9
+ Use GitHub's [Report a vulnerability](https://github.com/NomenAK/jev-tools/security/advisories/new) form. Do not disclose vulnerabilities in public issues or pull requests. If the form is unavailable, open an issue asking to restore private reporting, without vulnerability details. There is no email reporting channel.
10
+
11
+ Include the affected version, impact, and a minimal reproduction using synthetic data. Never include live credentials, private repository contents, or personal configuration. If a credential may have been exposed, revoke or rotate it independently of this report.
12
+
13
+ This is a solo-maintained project. Reports are reviewed on a best-effort basis; there is no guaranteed acknowledgement or fix deadline. Follow-up and disclosure coordination take place in the private report. Please coordinate public disclosure there.
14
+
15
+ ## Scope and trust boundaries
16
+
17
+ Report repository-confinement escapes in file evidence collection, including reads outside the selected repository; unintended secret exposure caused by jev-tools; and unintended command execution or bypasses of disabled command execution.
18
+
19
+ File evidence collection is confined to the repository, but optional commands are not sandboxed: they run with the host's permissions and may read or modify files or access the network. `JEV_TOOLS_ALLOW_COMMAND=0` disables this command path. Selected repository evidence and requested command output are sent to the configured API endpoint; automatic secret redaction is not promised. Review the data and endpoint before use.
20
+
21
+ Saved interactive configuration stores the API key in plaintext in a private user-level file (`~/.config/jev-agent-tools/config.json` by default). Privacy uses each operating system's own model: owner-only mode bits on POSIX; on Windows, an access list whose allow entries are only the current user, SYSTEM and Administrators, read by SID through the system Windows PowerShell. Protect the account and back-ups accordingly, or use environment variables from a secret manager instead.
22
+
23
+ The MCP server (`jev-agent-tools-mcp`) has the same boundaries. It talks to its client only over stdio and opens no network listener. MCP client configuration files that contain a literal API key are as sensitive as the saved configuration; prefer the client's environment-variable interpolation and do not commit such files. Tool approval is the MCP client's responsibility; the server marks `jev_ask` as not read-only while commands are enabled.
24
+
25
+ Command cancellation and timeout, and MCP connection shutdown, complete bounded termination of the managed process group on POSIX or await the system process-tree termination operation on Windows. This is cleanup, not containment: deliberately detached descendants or processes that escape that tree are not tracked, and previously completed side effects are not undone.
26
+
27
+ Expected execution of an explicitly supplied command, intentional submission of evidence, inaccurate judgments, and ordinary bugs or feature requests are not by themselves security vulnerabilities. Report ordinary product problems through issues; report host or API-service vulnerabilities to their respective maintainers. If unsure about a jev-tools security impact, use the private form.
@@ -0,0 +1,75 @@
1
+ import { createHash } from "node:crypto";
2
+ import { tokenize, } from "../core/lexical.js";
3
+ import { loadSyntaxRootParser } from "./syntax.js";
4
+ /** Owns one invocation's source versions; never retains repository data across calls. */
5
+ export async function createAnalysisContext(counter, sourceParser) {
6
+ const fingerprints = new Map();
7
+ const fingerprint = (text) => {
8
+ let value = fingerprints.get(text);
9
+ if (value === undefined) {
10
+ value = createHash("sha256").update(text).digest("hex");
11
+ fingerprints.set(text, value);
12
+ }
13
+ return value;
14
+ };
15
+ const lexical = new Map();
16
+ const tokenizeSource = (path, text, python = false) => {
17
+ const version = fingerprint(text);
18
+ let versions = lexical.get(path);
19
+ if (!versions) {
20
+ versions = new Map();
21
+ lexical.set(path, versions);
22
+ }
23
+ const key = `${version}:${python}`;
24
+ let tokens = versions.get(key);
25
+ if (!tokens) {
26
+ counter?.tokenized(path, version);
27
+ tokens = tokenize(text, python);
28
+ versions.set(key, tokens);
29
+ }
30
+ return tokens;
31
+ };
32
+ const source = sourceParser ?? (await loadSyntaxRootParser());
33
+ if (!source)
34
+ return {
35
+ parser: undefined,
36
+ fingerprint,
37
+ counter,
38
+ tokenize: tokenizeSource,
39
+ };
40
+ const roots = new Map();
41
+ const parser = {
42
+ supports: source.supports,
43
+ parseRaw(path, text) {
44
+ const version = fingerprint(text);
45
+ let versions = roots.get(path);
46
+ if (!versions) {
47
+ versions = new Map();
48
+ roots.set(path, versions);
49
+ }
50
+ let parsed = versions.get(version);
51
+ if (!parsed) {
52
+ counter?.parsed(path, version);
53
+ parsed = source.parseRaw(path, text);
54
+ versions.set(version, parsed);
55
+ }
56
+ return parsed;
57
+ },
58
+ parse(path, text) {
59
+ const parsed = this.parseRaw(path, text);
60
+ if (!parsed.ok)
61
+ return parsed;
62
+ return parsed.root.find({ rule: { kind: "ERROR" } })
63
+ ? { ok: false, error: "Source syntax incomplete" }
64
+ : parsed;
65
+ },
66
+ declarations: source.declarations,
67
+ };
68
+ return {
69
+ parser,
70
+ fingerprint,
71
+ counter,
72
+ tokenize: tokenizeSource,
73
+ resolved: (path, text) => counter?.resolved(path, fingerprint(text)),
74
+ };
75
+ }
@@ -0,0 +1,189 @@
1
+ import { readdir, stat } from "node:fs/promises";
2
+ import { dirname, isAbsolute, matchesGlob, relative, resolve } from "node:path";
3
+ import { CONCURRENCY, MAX_FILES, TIMEOUT_MS } from "../constants.js";
4
+ import { checkFileAdmission, collectFiles, resolveInsideRepo, } from "./files.js";
5
+ const excludedDirectories = new Set(["node_modules", ".git", "dist", "build"]);
6
+ const lockfile = /(?:^|\/)(?:package-lock\.json|npm-shrinkwrap\.json|yarn\.lock|pnpm-lock\.yaml|bun\.lockb?|Cargo\.lock|Gemfile\.lock|poetry\.lock|uv\.lock|composer\.lock|Pipfile\.lock|go\.sum)$/;
7
+ export async function collectAskFiles(cwd, paths, signal, exec) {
8
+ if (!paths.length)
9
+ return {
10
+ ok: false,
11
+ error: "Provide at least one path, directory or glob.",
12
+ };
13
+ for (const path of paths) {
14
+ if (/^[a-z][a-z0-9+.-]*:\/\//i.test(path))
15
+ return {
16
+ ok: false,
17
+ error: `Internal URLs are not files; use read for ${path}.`,
18
+ };
19
+ if (isAbsolute(path) || path.split(/[\\/]/).includes(".."))
20
+ return {
21
+ ok: false,
22
+ error: `Path must remain inside the repository: ${path}.`,
23
+ };
24
+ }
25
+ const skipped = new Set();
26
+ const candidates = new Set();
27
+ try {
28
+ let inventory;
29
+ if (exec) {
30
+ const result = await exec("git", ["ls-files", "--cached", "--others", "--exclude-standard", "-z"], { cwd, timeout: TIMEOUT_MS, signal });
31
+ if (result.code === 0)
32
+ inventory = new Set(result.stdout.split("\0").filter(Boolean));
33
+ }
34
+ const add = async (path) => {
35
+ const admission = await checkFileAdmission(cwd, path, exec, signal, inventory);
36
+ if (!admission.ok) {
37
+ skipped.add(`${path} (${admission.error})`);
38
+ return;
39
+ }
40
+ if (path.split("/").some((part) => excludedDirectories.has(part)))
41
+ skipped.add(`${path} (build output or dependencies)`);
42
+ else if (lockfile.test(path))
43
+ skipped.add(`${path} (lockfile)`);
44
+ else
45
+ candidates.add(path);
46
+ };
47
+ const walk = async (directory, pattern) => {
48
+ signal?.throwIfAborted();
49
+ const safe = await resolveInsideRepo(cwd, directory);
50
+ if (!safe.ok) {
51
+ skipped.add(`${directory} (${safe.error})`);
52
+ return;
53
+ }
54
+ const entries = await readdir(safe.abs, { withFileTypes: true });
55
+ await Promise.all(entries.map(async (entry) => {
56
+ const path = relative(cwd, resolve(cwd, directory, entry.name)).replaceAll("\\", "/");
57
+ if (entry.isSymbolicLink()) {
58
+ skipped.add(`${path} (symbolic link)`);
59
+ return;
60
+ }
61
+ if (entry.isDirectory()) {
62
+ if (excludedDirectories.has(entry.name)) {
63
+ skipped.add(`${path}/ (build output or dependencies)`);
64
+ return;
65
+ }
66
+ await walk(path, pattern);
67
+ return;
68
+ }
69
+ if (entry.isFile() &&
70
+ (!pattern ||
71
+ matchesGlob(path
72
+ .split("/")
73
+ .map((part) => part.startsWith(".") ? `__dot__${part.slice(1)}` : part)
74
+ .join("/"), pattern
75
+ .split("/")
76
+ .map((part) => part.startsWith(".") ? `__dot__${part.slice(1)}` : part)
77
+ .join("/"))))
78
+ await add(path);
79
+ }));
80
+ };
81
+ for (const input of paths) {
82
+ signal?.throwIfAborted();
83
+ const safe = await resolveInsideRepo(cwd, input);
84
+ const info = safe.ok
85
+ ? await stat(safe.abs).catch(() => undefined)
86
+ : undefined;
87
+ if (info && safe.ok) {
88
+ if (info.isFile()) {
89
+ const admission = await checkFileAdmission(cwd, safe.rel, exec, signal, inventory);
90
+ if (!admission.ok)
91
+ return admission;
92
+ }
93
+ if (input.split("/").some((part) => excludedDirectories.has(part))) {
94
+ skipped.add(`${input} (build output or dependencies)`);
95
+ continue;
96
+ }
97
+ if (info.isDirectory())
98
+ await walk(safe.rel || ".");
99
+ else if (info.isFile()) {
100
+ const admission = await checkFileAdmission(cwd, safe.rel, exec, signal, inventory);
101
+ if (!admission.ok)
102
+ return admission;
103
+ await add(safe.rel);
104
+ }
105
+ else
106
+ skipped.add(`${input} (not a regular file)`);
107
+ }
108
+ else {
109
+ const parts = input.split("/");
110
+ const wildcard = parts.findIndex((part) => /[?*[\]{}]/.test(part));
111
+ if (wildcard < 0) {
112
+ if (!safe.ok && !safe.error.includes("ENOENT"))
113
+ return safe;
114
+ skipped.add(`${input} (missing)`);
115
+ continue;
116
+ }
117
+ const prefix = parts.slice(0, wildcard).join("/") || ".";
118
+ const prefixLocation = await resolveInsideRepo(cwd, prefix);
119
+ if (!prefixLocation.ok)
120
+ return prefixLocation;
121
+ const pattern = input.replace(/^\.\//, "");
122
+ if (inventory) {
123
+ for (const path of inventory) {
124
+ const dotPath = path
125
+ .split("/")
126
+ .map((part) => part.startsWith(".") ? `__dot__${part.slice(1)}` : part)
127
+ .join("/");
128
+ const dotPattern = pattern
129
+ .split("/")
130
+ .map((part) => part.startsWith(".") ? `__dot__${part.slice(1)}` : part)
131
+ .join("/");
132
+ if (matchesGlob(dotPath, dotPattern))
133
+ await add(path);
134
+ }
135
+ }
136
+ else
137
+ await walk(prefix, pattern);
138
+ }
139
+ }
140
+ const files = [];
141
+ const sorted = [...candidates].sort();
142
+ const contributions = new Map();
143
+ for (const path of sorted) {
144
+ const directory = dirname(path);
145
+ contributions.set(directory, (contributions.get(directory) ?? 0) + 1);
146
+ }
147
+ let accepted = 0;
148
+ for (let i = 0; i < sorted.length; i += CONCURRENCY) {
149
+ const rows = await Promise.all(sorted.slice(i, i + CONCURRENCY).map(async (path) => {
150
+ const result = await collectFiles(cwd, [path], signal, {
151
+ exec,
152
+ inventory,
153
+ });
154
+ if (!result.ok) {
155
+ skipped.add(`${path} (${result.error})`);
156
+ return;
157
+ }
158
+ const identity = result.identities[0];
159
+ if (!identity)
160
+ return;
161
+ if (identity.content.includes("\0") ||
162
+ identity.readSha256 !== identity.insertedSha256) {
163
+ skipped.add(`${path} (binary or invalid UTF-8)`);
164
+ return;
165
+ }
166
+ return { path, content: identity.content, identity };
167
+ }));
168
+ for (const file of rows)
169
+ if (file) {
170
+ accepted++;
171
+ if (files.length < MAX_FILES)
172
+ files.push(file);
173
+ }
174
+ if (accepted > MAX_FILES) {
175
+ const largest = [...contributions]
176
+ .sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
177
+ .slice(0, 5);
178
+ return {
179
+ ok: false,
180
+ error: `At least ${accepted} files exceeds MAX_FILES=${MAX_FILES}; narrow paths. Largest expanded directories: ${largest.map(([path, count]) => `${path}: ${count}`).join(", ")}.`,
181
+ };
182
+ }
183
+ }
184
+ return { ok: true, files, skipped: [...skipped].sort() };
185
+ }
186
+ catch (error) {
187
+ return { ok: false, error: `Cannot expand paths: ${String(error)}` };
188
+ }
189
+ }