jev-agent-tools 0.2.0 → 0.3.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.
- package/CHANGELOG.md +37 -1
- package/CONTRIBUTING.md +3 -0
- package/README.md +22 -14
- package/SECURITY.md +17 -1
- package/dist/adapters/ask-files.js +11 -2
- package/dist/adapters/ask-proof.js +63 -7
- package/dist/adapters/command.js +82 -29
- package/dist/adapters/docs.js +30 -10
- package/dist/adapters/evidence-context.js +119 -0
- package/dist/adapters/files.js +141 -16
- package/dist/adapters/find.js +34 -6
- package/dist/adapters/git-base.js +7 -1
- package/dist/adapters/git.js +51 -7
- package/dist/adapters/locate-file.js +47 -9
- package/dist/adapters/private-storage.js +14 -6
- package/dist/adapters/risk-callers.js +3 -0
- package/dist/adapters/shell.js +23 -7
- package/dist/adapters/test-inventory.js +10 -2
- package/dist/configuration.js +17 -7
- package/dist/constants.js +26 -5
- package/dist/core/ask-references.js +193 -109
- package/dist/core/asks.js +78 -7
- package/dist/core/locate.js +8 -8
- package/dist/core/output.js +17 -0
- package/dist/core/result-report.js +302 -0
- package/dist/core/secret-path.js +34 -0
- package/dist/core/state.js +8 -1
- package/dist/core/units.js +1 -1
- package/dist/jev/client.js +34 -12
- package/dist/mcp/protocol.js +50 -27
- package/dist/mcp/tools.js +20 -7
- package/dist/render.js +72 -0
- package/dist/report-schema.js +1356 -0
- package/dist/result-types.js +1 -0
- package/dist/texts/ask-files.js +3 -1
- package/dist/texts/ask.js +3 -1
- package/dist/texts/check-diff.js +7 -4
- package/dist/texts/find.js +7 -2
- package/dist/texts/guide.js +3 -16
- package/dist/texts/instructions.js +72 -0
- package/dist/texts/locate.js +7 -2
- package/dist/texts/select-tests.js +3 -1
- package/dist/tools/ask-files.js +248 -15
- package/dist/tools/ask.js +523 -62
- package/dist/tools/check-diff.js +222 -30
- package/dist/tools/docs-check.js +122 -13
- package/dist/tools/find.js +320 -27
- package/dist/tools/locate.js +317 -18
- package/dist/tools/review-report.js +230 -0
- package/dist/tools/select-tests.js +273 -19
- package/dist/tools/spec-check.js +119 -22
- package/docs/adr/0001-strict-typescript-pure-core-offline-tests.md +3 -3
- package/docs/agent-instructions.md +59 -30
- package/docs/design.md +13 -1
- package/docs/mcp.md +8 -6
- package/docs/tools/jev_ask.md +8 -5
- package/docs/tools/jev_ask_files.md +2 -1
- package/docs/tools/jev_check_diff.md +4 -1
- package/docs/tools/jev_find_files.md +2 -1
- package/docs/tools/jev_locate_in_file.md +5 -0
- package/docs/tools/jev_select_tests.md +4 -1
- package/package.json +1 -1
- package/rules/jev-ask.md +22 -1
- package/server.json +2 -2
- package/src/adapters/ask-files.ts +11 -3
- package/src/adapters/ask-proof.ts +69 -11
- package/src/adapters/command.ts +96 -33
- package/src/adapters/docs.ts +33 -14
- package/src/adapters/evidence-context.ts +169 -0
- package/src/adapters/files.ts +146 -16
- package/src/adapters/find.ts +37 -7
- package/src/adapters/git-base.ts +7 -1
- package/src/adapters/git.ts +61 -8
- package/src/adapters/locate-file.ts +51 -9
- package/src/adapters/private-storage.ts +17 -5
- package/src/adapters/risk-callers.ts +3 -0
- package/src/adapters/shell.ts +23 -7
- package/src/adapters/test-inventory.ts +12 -4
- package/src/configuration.ts +16 -2
- package/src/constants.ts +26 -5
- package/src/core/ask-references.ts +262 -146
- package/src/core/asks.ts +79 -7
- package/src/core/import-boundaries.ts +8 -3
- package/src/core/locate.ts +8 -5
- package/src/core/output.ts +34 -0
- package/src/core/result-report.ts +410 -0
- package/src/core/secret-path.ts +37 -0
- package/src/core/state.ts +8 -1
- package/src/core/units.ts +3 -2
- package/src/index.ts +3 -0
- package/src/jev/client.ts +54 -16
- package/src/jev/types.ts +18 -3
- package/src/mcp/protocol.ts +91 -41
- package/src/mcp/tools.ts +26 -13
- package/src/render.ts +109 -0
- package/src/report-schema.ts +1380 -0
- package/src/result-types.ts +234 -0
- package/src/result.ts +4 -1
- package/src/runtime.ts +6 -0
- package/src/texts/ask-files.ts +4 -1
- package/src/texts/ask.ts +8 -1
- package/src/texts/check-diff.ts +7 -4
- package/src/texts/find.ts +8 -2
- package/src/texts/guide.ts +8 -16
- package/src/texts/instructions.ts +98 -0
- package/src/texts/locate.ts +8 -2
- package/src/texts/run-end.ts +2 -2
- package/src/texts/select-tests.ts +4 -1
- package/src/tools/ask-files.ts +309 -14
- package/src/tools/ask.ts +700 -77
- package/src/tools/check-diff.ts +331 -28
- package/src/tools/docs-check.ts +241 -39
- package/src/tools/find.ts +386 -29
- package/src/tools/locate.ts +384 -19
- package/src/tools/review-report.ts +308 -0
- package/src/tools/select-tests.ts +479 -21
- package/src/tools/spec-check.ts +193 -19
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,41 @@ modules are not a stable library API.
|
|
|
9
9
|
|
|
10
10
|
## [Unreleased]
|
|
11
11
|
|
|
12
|
+
## [0.3.0] - 2026-10-05
|
|
13
|
+
|
|
14
|
+
### Breaking
|
|
15
|
+
|
|
16
|
+
- `JEV_TOOLS_URL` (and saved or flag URLs) with plain `http:` is refused unless the host is `127.0.0.1`, `::1` or `localhost`; use `https:`. The configuration error shows neither the URL nor the key and occurs before any request.
|
|
17
|
+
- Files whose base name matches `.env`, `.env.*`, `*.pem`, `id_rsa*`, `*.p12`, `credentials*` or `secrets*` (any depth, case-insensitive; `.env.example`, `.env.sample` and `.env.template` excepted) are refused before reading on every evidence path, including diff units, test inventory, find, locate, ask-files and the pi/omp documentation hook. Each refusal is a named exclusion with the new cause `secret_pattern`; a question that explicitly requires such a file stays unjudged. There is no override.
|
|
18
|
+
|
|
19
|
+
### Security
|
|
20
|
+
|
|
21
|
+
- `jev_ask` commands and the Windows PowerShell ACL helper no longer inherit `JEV_TOOLS_API_KEY`. The configured key value (environment or saved configuration) is replaced with `[redacted]` in command output before state assembly, including a key prefix left where a long output line is cut, and the number of replacements is reported as a limitation.
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
|
|
25
|
+
- Optional `root` on all six tools, limited to the initial repository's exact Git root or registered live worktrees with the same common directory; invalid overrides refuse before evidence, commands, cache or judgments.
|
|
26
|
+
- Versioned typed reports for every tool outcome, including refused and unjudged work: evidence context, item provenance, scoped diagnostics/actions and separate requested-result, HTTP, cache, control, passage and cost accounting.
|
|
27
|
+
- MCP output schema and structured results from protocol 2025-06-18 onward, with self-contained text for older clients and per-request version isolation.
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
|
|
31
|
+
- Explicit evidence selectors, rather than ordinary lexical mentions, govern missing-evidence exclusions. Canonical file versions are serialized once with alias metadata; before/current evidence remains distinct and missing requirements affect their own question group unless global.
|
|
32
|
+
- Host guidance now makes Jev discretionary, shares versioned canonical fragments across pi/omp/MCP, and reports current conclusions with evidence provenance and material reservations. The pi/omp opt-out documentation hook remains; MCP requires no manual replacement call.
|
|
33
|
+
- Human output is a projection of the typed report. Static/fallback selection and unavailable judgments never acquire fabricated probabilities; cached results are distinct from fresh requests and unknown cost is not zero.
|
|
34
|
+
|
|
35
|
+
### Fixed
|
|
36
|
+
|
|
37
|
+
- Historical-only and deleted-file evidence resolution, supplied-file basename precedence and canonical file-count admission without duplicated alias content.
|
|
38
|
+
- Command output reaches bounded passage selection before immutable-evidence budget refusal, including base snapshots and import closure; reports preserve actual command execution/cwd even when later collection fails.
|
|
39
|
+
- Selection reports distinguish unmatched candidate criteria, partial matches, excluded inventory and named conservative-widening triggers from absence of affected tests.
|
|
40
|
+
- Review reports retain documentation completeness/collection reservations and risk/coverage witness diagnostics. Residual absence summaries remain derived observations within the considered inventory, not extra judgments or global coverage claims.
|
|
41
|
+
- Recovery actions preserve the actual evidence/control limitation instructions rather than generic repetition advice.
|
|
42
|
+
- Local-caller reports preserve their independent unsure/abstain bands and matching probabilities; specification drift remains one pointer decision rather than duplicated judgments for every candidate unit.
|
|
43
|
+
- MCP malformed tool arguments remain JSON-RPC invalid-parameter errors, separate from well-formed calls refused by evidence admission.
|
|
44
|
+
- Every judgment stage binds cache identity and serialized admission to its admitted root/base context, including auxiliary command passages. Locate planning reserves that metadata capacity before allocating evidence.
|
|
45
|
+
- Historical selectors use protected base reads for ignored paths, symlinks, non-text content and deleted files; current/base versions share the distinct-file ceiling without bypassing rejected admissions.
|
|
46
|
+
|
|
12
47
|
## [0.2.0] - 2026-10-03
|
|
13
48
|
|
|
14
49
|
### Added
|
|
@@ -142,7 +177,8 @@ Tagged but never published to npm: the unscoped package name was rejected.
|
|
|
142
177
|
|
|
143
178
|
Tagged but never published to npm: the publish workflow failed before upload.
|
|
144
179
|
|
|
145
|
-
[Unreleased]: https://github.com/NomenAK/jev-tools/compare/v0.
|
|
180
|
+
[Unreleased]: https://github.com/NomenAK/jev-tools/compare/v0.3.0...HEAD
|
|
181
|
+
[0.3.0]: https://github.com/NomenAK/jev-tools/compare/v0.2.0...v0.3.0
|
|
146
182
|
[0.2.0]: https://github.com/NomenAK/jev-tools/compare/v0.1.4...v0.2.0
|
|
147
183
|
[0.1.4]: https://github.com/NomenAK/jev-tools/compare/v0.1.3...v0.1.4
|
|
148
184
|
[0.1.3]: https://github.com/NomenAK/jev-tools/compare/v0.1.0...v0.1.3
|
package/CONTRIBUTING.md
CHANGED
|
@@ -18,11 +18,14 @@ npm run typecheck
|
|
|
18
18
|
npm run build
|
|
19
19
|
npm run check:imports
|
|
20
20
|
npm run lint
|
|
21
|
+
node scripts/generate-instructions.ts --check
|
|
21
22
|
npm test
|
|
22
23
|
```
|
|
23
24
|
|
|
24
25
|
`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
|
|
|
27
|
+
Agent policy and result-reading guidance are versioned in `src/texts/instructions.ts`. After changing them, run `node scripts/generate-instructions.ts` to update the checked-in omp rule and MCP project block; `--check` verifies that those copies remain synchronized. Keep host differences explicit rather than maintaining independent policy text.
|
|
28
|
+
|
|
26
29
|
## Releases
|
|
27
30
|
|
|
28
31
|
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.
|
package/README.md
CHANGED
|
@@ -13,15 +13,15 @@ Install through your host's package manager, or register the MCP server with an
|
|
|
13
13
|
### pi
|
|
14
14
|
|
|
15
15
|
```sh
|
|
16
|
-
pi install npm:jev-agent-tools@0.
|
|
16
|
+
pi install npm:jev-agent-tools@0.3.0
|
|
17
17
|
# Project-local installation:
|
|
18
|
-
pi install -l npm:jev-agent-tools@0.
|
|
18
|
+
pi install -l npm:jev-agent-tools@0.3.0
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
### omp
|
|
22
22
|
|
|
23
23
|
```sh
|
|
24
|
-
omp plugin install jev-agent-tools@0.
|
|
24
|
+
omp plugin install jev-agent-tools@0.3.0
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
### Any MCP client
|
|
@@ -45,7 +45,7 @@ Since version 0.2.0, the package also ships `jev-agent-tools-mcp`, a stdio MCP s
|
|
|
45
45
|
|
|
46
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
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.
|
|
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. MCP has no automatic run-end documentation hook; its absence does not require manual replacement calls. `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
49
|
|
|
50
50
|
### Requirements and compatibility
|
|
51
51
|
|
|
@@ -90,8 +90,8 @@ export JEV_TOOLS_MODEL="openjev"
|
|
|
90
90
|
|
|
91
91
|
| Variable | Meaning |
|
|
92
92
|
|---|---|
|
|
93
|
-
| `JEV_TOOLS_URL` | Required complete endpoint URL compatible with the Jev API format. |
|
|
94
|
-
| `JEV_TOOLS_API_KEY` | Required Bearer credential; configuration values are not printed in tool output. |
|
|
93
|
+
| `JEV_TOOLS_URL` | Required complete endpoint URL compatible with the Jev API format. Must be `https:`; plain `http:` is accepted only for `127.0.0.1`, `::1` or `localhost`. |
|
|
94
|
+
| `JEV_TOOLS_API_KEY` | Required Bearer credential; configuration values are not printed in tool output, and command children never receive it. |
|
|
95
95
|
| `JEV_TOOLS_MODEL` | Requested model string, default `openjev`; a moving alias, not a guarantee of served-model identity. |
|
|
96
96
|
| `JEV_TOOLS_MAX_CALLS` | Session-wide non-negative safe-integer call limit; absent or empty means unlimited. Invalid values refuse requests. |
|
|
97
97
|
| `JEV_TOOLS_MAX_USD` | Session-wide finite non-negative cost limit, including fractions; absent or empty means unlimited. Invalid values refuse requests. |
|
|
@@ -115,29 +115,37 @@ Without the endpoint or key, tools remain registered and explain the missing con
|
|
|
115
115
|
|
|
116
116
|
Use native read/search tools or code for exact source text, known symbols, filenames, line numbers, counts and arithmetic. Run commands yourself when you need their full output. Tool reference examples use fictional repository paths and are illustrative calls, not recorded executions.
|
|
117
117
|
|
|
118
|
+
### Evidence root
|
|
119
|
+
|
|
120
|
+
All six tools accept optional `root: string`. Without it, the host's current directory or configured MCP server directory retains its existing behavior. An override must name exactly the initial repository's Git top-level or a registered live worktree with the same canonical Git common directory. Relative overrides resolve against the initial directory; absolute paths are accepted only for this parameter. Subdirectories, other clones/repositories, parent traversal and symlink components are refused before evidence collection, command execution, cache access or judgment. There is no fallback to a different checkout.
|
|
121
|
+
|
|
122
|
+
The admitted root governs files, base/diff, inventories, specification paths, runner plans and command cwd for that call only. It does not change another call or the automatic documentation hook. File arguments remain repository-relative and confined. A command still has ordinary shell permissions, not a sandbox. Check the reported authority, requested/effective root and resolved base before using a result.
|
|
123
|
+
|
|
118
124
|
## Read the results
|
|
119
125
|
|
|
120
|
-
|
|
126
|
+
Each result begins with execution state: **complete**, **partial**, **not_judged** or **refused**. This describes processing, not correctness, safety or coverage. Items distinguish fresh or cached Jev judgments, static treatment and work never judged. Static selection and conservative fallback carry no invented probability. A judged line without an uncertainty mark is a **verdict**: a lead to check, not proof beyond the supplied evidence.
|
|
121
127
|
|
|
122
128
|
| Mark | Meaning and next action |
|
|
123
129
|
|---|---|
|
|
124
130
|
| `unsure` | The answer is ambiguous or a control failed. Read the indicated passage or add the specific evidence that would settle it. Do not merely reword the question. |
|
|
125
|
-
| `abstain` | A necessary piece is missing.
|
|
131
|
+
| `abstain` | A necessary piece is missing. Obtain the named evidence or leave the conclusion open; another Jev call is optional when the changed evidence makes it useful. |
|
|
126
132
|
| `no (not shown)` / `not addressed` | The supplied evidence does not show the statement; that does not make it false. |
|
|
127
133
|
| `uncalibrated` | No established error-rate calibration applies to this ask; treat it as a hint even if its probability is high. |
|
|
128
|
-
|
|
|
134
|
+
| Diagnostics and next actions | Typed cause, origin, affected scope, materiality and recovery instructions; distinguish uncertain judgments from work never judged. |
|
|
129
135
|
|
|
130
136
|
Ordinary boolean verdict bands are at or below 0.20 and at or above 0.80; category/level verdicts require a leading-option probability of at least 0.85 after applicable controls. Fixed checks and navigation tools have their own thresholds, described in their references and [design](docs/design.md).
|
|
131
137
|
|
|
132
|
-
|
|
138
|
+
Accounting separates **HTTP attempts**, **questions sent**, **requested results** (fresh/cache/static/not judged), **cache probes**, auxiliary controls and passage selection, current reported USD cost and elapsed time. These counts are not interchangeable. A cached judgment can have zero HTTP attempts; zero attempts can also mean static work or no judgment. Unknown cost is unreported, not free. Context values distinguish known, unknown, not collected and not applicable.
|
|
139
|
+
|
|
140
|
+
pi and omp expose the versioned report as `details.result`. MCP versions from 2025-06-18 expose the same report under `structuredContent.result` with an advertised output schema; older versions receive self-contained text from the same report. Text preserves material limitations, evidence provenance and useful read/runner commands.
|
|
133
141
|
|
|
134
|
-
|
|
142
|
+
Report current conclusions, decisive evidence with origin and scope, and material reservations. If native reading or execution settles an earlier `unsure` or `abstain` on the same context, attribute the current conclusion to that native evidence, not Jev. If uncertainty returned by Jev remains material, explicitly say Jev did not confirm the conclusion and name the missing evidence and impact. Independent limits, stale evidence and conflicting contexts remain visible. Reported checks are not observed execution; no exhaustive history block or new persistent register is required.
|
|
135
143
|
|
|
136
144
|
## Usage guidance for pi and omp
|
|
137
145
|
|
|
138
146
|
The extension supplies the shared reading guide in both hosts. omp discovers enabled npm plugin rules during normal startup; pi does not automatically discover the package's `rules/` directory. In pi, the same decision policy is part of the `jev_ask` tool guidelines, so it is present whenever `jev_ask` is active. A forced opaque prompt override may bypass this integration; disabled tools or disabled omp rules are not covered. This README block is recommended usage guidance, not itself an installed instruction:
|
|
139
147
|
|
|
140
|
-
>
|
|
148
|
+
> Use Jev for a bounded semantic judgment when it can change an open decision or focus inspection. Use decisive native reading, search or authorized execution directly. A Jev call is not a prerequisite for a conclusion, review or completion. When choosing a call, supply both sides of a comparison and the evidence that distinguishes explanations. Revisit only when changed evidence, context or a useful new question warrants it; repeating unchanged evidence is not a recovery action.
|
|
141
149
|
|
|
142
150
|
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
151
|
|
|
@@ -145,11 +153,11 @@ MCP clients receive the guide as server `instructions`, which some clients ignor
|
|
|
145
153
|
|
|
146
154
|
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).
|
|
147
155
|
|
|
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.
|
|
156
|
+
File collection is confined to the repository: absolute paths, parent traversal, escaping symlinks, Git metadata and internal URLs are not file inputs. Files named like secrets (`.env`, `.env.*` except `.env.example`/`.env.sample`/`.env.template`, `*.pem`, `id_rsa*`, `*.p12`, `credentials*`, `secrets*`, any depth, any case) are refused before reading and named with cause `secret_pattern`. 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; they run without `JEV_TOOLS_API_KEY`, and the configured key is replaced with `[redacted]` in their output. 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.
|
|
149
157
|
|
|
150
158
|
## Automatic documentation check
|
|
151
159
|
|
|
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.
|
|
160
|
+
On a dirty tree, the extension can check existing Markdown documentation once at run end against changes from `HEAD`, including admitted untracked files (not gitignored, not secret-named), which are sent to the endpoint without an explicit tool call. A flagged existing sentence can request one additional turn to inspect and update it or explain with evidence why it remains correct; a flag does not itself establish falsehood or require a second call. Merely unsure sections do not trigger another turn. `JEV_TOOLS_AUTO_DOCS=0`, missing configuration, invalid/exhausted session budgets or a clean tree skip the check. Errors and timeout do not block the host. This opt-out host feature does not certify documentation completeness. MCP has no hook and requires no manual replacement ritual.
|
|
153
161
|
|
|
154
162
|
## Known limits
|
|
155
163
|
|
package/SECURITY.md
CHANGED
|
@@ -16,7 +16,23 @@ This is a solo-maintained project. Reports are reviewed on a best-effort basis;
|
|
|
16
16
|
|
|
17
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
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
|
|
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. Review the data and endpoint before use.
|
|
20
|
+
|
|
21
|
+
### Secret-named files are refused
|
|
22
|
+
|
|
23
|
+
Every evidence admission path (`jev_ask` paths and import closure, `jev_ask_files`, `jev_find_files`, `jev_locate_in_file`, `jev_check_diff` diff units and local callers, `jev_select_tests` inventory and the automatic pi/omp documentation check) refuses a file whose base name matches `.env`, `.env.*`, `*.pem`, `id_rsa*`, `*.p12`, `credentials*` or `secrets*`, at any depth and case-insensitively, whatever its Git status (tracked, untracked or added). `.env.example`, `.env.sample` and `.env.template` remain admitted. The refusal happens before the content is read: the result names the path with cause `secret_pattern`, and a question that explicitly requires that file stays unjudged. There is no per-call or environment override. Files with other names are not inspected for secrets.
|
|
24
|
+
|
|
25
|
+
### Commands never receive the API key
|
|
26
|
+
|
|
27
|
+
`jev_ask` commands and the Windows PowerShell ACL helper run with the host environment minus `JEV_TOOLS_API_KEY`. Before command output enters a state, every occurrence of the configured key value, whether it came from the environment or the saved configuration, is replaced with `[redacted]` in stdout, stderr and the echoed command line; the result reports how many replacements were made. Detection of other secrets in command output is not promised.
|
|
28
|
+
|
|
29
|
+
### The key travels only over HTTPS or loopback
|
|
30
|
+
|
|
31
|
+
`JEV_TOOLS_URL` must use `https:`. Plain `http:` is accepted only for `127.0.0.1`, `::1` or `localhost`; any other `http:` URL is a configuration error, reported without the URL or key and before any request, so the Bearer key is never sent in clear text over a network.
|
|
32
|
+
|
|
33
|
+
### Automatic documentation check
|
|
34
|
+
|
|
35
|
+
On pi and omp, the run-end documentation check (disable with `JEV_TOOLS_AUTO_DOCS=0`) sends changed units from a dirty tree to the endpoint without an explicit tool call, including admitted untracked files that are not gitignored. Secret-named files are refused as above; anything else untracked and not ignored can be sent. Keep sensitive scratch files gitignored or outside the repository.
|
|
20
36
|
|
|
21
37
|
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
38
|
|
|
@@ -23,6 +23,7 @@ export async function collectAskFiles(cwd, paths, signal, exec) {
|
|
|
23
23
|
};
|
|
24
24
|
}
|
|
25
25
|
const skipped = new Set();
|
|
26
|
+
const secret = new Set();
|
|
26
27
|
const candidates = new Set();
|
|
27
28
|
try {
|
|
28
29
|
let inventory;
|
|
@@ -34,7 +35,10 @@ export async function collectAskFiles(cwd, paths, signal, exec) {
|
|
|
34
35
|
const add = async (path) => {
|
|
35
36
|
const admission = await checkFileAdmission(cwd, path, exec, signal, inventory);
|
|
36
37
|
if (!admission.ok) {
|
|
37
|
-
|
|
38
|
+
const entry = `${path} (${admission.error})`;
|
|
39
|
+
skipped.add(entry);
|
|
40
|
+
if (admission.cause === "secret_pattern")
|
|
41
|
+
secret.add(entry);
|
|
38
42
|
return;
|
|
39
43
|
}
|
|
40
44
|
if (path.split("/").some((part) => excludedDirectories.has(part)))
|
|
@@ -181,7 +185,12 @@ export async function collectAskFiles(cwd, paths, signal, exec) {
|
|
|
181
185
|
};
|
|
182
186
|
}
|
|
183
187
|
}
|
|
184
|
-
return {
|
|
188
|
+
return {
|
|
189
|
+
ok: true,
|
|
190
|
+
files,
|
|
191
|
+
skipped: [...skipped].sort(),
|
|
192
|
+
secret: [...secret].sort(),
|
|
193
|
+
};
|
|
185
194
|
}
|
|
186
195
|
catch (error) {
|
|
187
196
|
return { ok: false, error: `Cannot expand paths: ${String(error)}` };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { relative, resolve } from "node:path";
|
|
1
|
+
import { isAbsolute, relative, resolve } from "node:path";
|
|
2
2
|
import { STATE_MAX_CHARS, TIMEOUT_MS } from "../constants.js";
|
|
3
|
-
import { collectFiles } from "./files.js";
|
|
3
|
+
import { checkFileAdmission, collectFiles } from "./files.js";
|
|
4
4
|
import { verifyGitUtf8 } from "./utf8.js";
|
|
5
5
|
export async function collectAskRepository(exec, cwd, base, signal) {
|
|
6
6
|
try {
|
|
@@ -37,20 +37,25 @@ export async function collectAskRepository(exec, cwd, base, signal) {
|
|
|
37
37
|
known.delete(path);
|
|
38
38
|
const baseSha = revision?.stdout.trim();
|
|
39
39
|
const basePaths = baseSha
|
|
40
|
-
? await exec("git", ["ls-tree", "-r", "
|
|
40
|
+
? await exec("git", ["ls-tree", "-r", "-z", baseSha], gitOptions)
|
|
41
41
|
: undefined;
|
|
42
42
|
if (basePaths && (basePaths.code !== 0 || basePaths.killed))
|
|
43
43
|
return {
|
|
44
44
|
ok: false,
|
|
45
45
|
error: basePaths.stderr || "Cannot list base paths.",
|
|
46
46
|
};
|
|
47
|
-
const
|
|
47
|
+
const beforeEntries = new Map((basePaths?.stdout.split("\0").filter(Boolean) ?? []).map((entry) => [
|
|
48
|
+
entry.slice(entry.indexOf("\t") + 1),
|
|
49
|
+
entry,
|
|
50
|
+
]));
|
|
51
|
+
const beforeKnown = new Set(beforeEntries.keys());
|
|
48
52
|
const reads = new Map();
|
|
49
53
|
const beforeReads = new Map();
|
|
50
54
|
return {
|
|
51
55
|
ok: true,
|
|
52
56
|
root,
|
|
53
57
|
known,
|
|
58
|
+
beforeKnown,
|
|
54
59
|
baseSha,
|
|
55
60
|
read(path) {
|
|
56
61
|
let pending = reads.get(path);
|
|
@@ -78,9 +83,50 @@ export async function collectAskRepository(exec, cwd, base, signal) {
|
|
|
78
83
|
let pending = beforeReads.get(path);
|
|
79
84
|
if (!pending) {
|
|
80
85
|
pending = (async () => {
|
|
81
|
-
if (!baseSha || !beforeKnown.has(path))
|
|
82
|
-
return { ok: true, text: null };
|
|
83
86
|
try {
|
|
87
|
+
if (isAbsolute(path) || path.split(/[\\/]/).includes(".."))
|
|
88
|
+
return {
|
|
89
|
+
ok: false,
|
|
90
|
+
cause: "forbidden_path",
|
|
91
|
+
error: `Path must remain inside the repository: ${path}.`,
|
|
92
|
+
};
|
|
93
|
+
const admission = await checkFileAdmission(root, path);
|
|
94
|
+
if (!admission.ok)
|
|
95
|
+
return admission;
|
|
96
|
+
const ignoredPath = await exec("git", ["check-ignore", "--no-index", "--", path], gitOptions);
|
|
97
|
+
if (ignoredPath.killed || ![0, 1].includes(ignoredPath.code))
|
|
98
|
+
return {
|
|
99
|
+
ok: false,
|
|
100
|
+
cause: "git_failure",
|
|
101
|
+
error: `${path}: historical admission unavailable`,
|
|
102
|
+
};
|
|
103
|
+
if (ignoredPath.code === 0)
|
|
104
|
+
return {
|
|
105
|
+
ok: false,
|
|
106
|
+
cause: "ignored_path",
|
|
107
|
+
error: `${path}: gitignored: not sent to Jev`,
|
|
108
|
+
};
|
|
109
|
+
if (!baseSha)
|
|
110
|
+
return {
|
|
111
|
+
ok: false,
|
|
112
|
+
error: `${path}: comparison base unavailable`,
|
|
113
|
+
};
|
|
114
|
+
if (!beforeKnown.has(path))
|
|
115
|
+
return known.has(path)
|
|
116
|
+
? { ok: true, text: null }
|
|
117
|
+
: {
|
|
118
|
+
ok: false,
|
|
119
|
+
error: `${path}: not in current or comparison base inventory`,
|
|
120
|
+
};
|
|
121
|
+
const entry = beforeEntries.get(path) ?? "";
|
|
122
|
+
if (!/^(?:100644|100755) blob /.test(entry))
|
|
123
|
+
return {
|
|
124
|
+
ok: false,
|
|
125
|
+
cause: entry.startsWith("120000 ")
|
|
126
|
+
? "symlink"
|
|
127
|
+
: "file_unavailable",
|
|
128
|
+
error: `${path}: not a regular file at comparison base: not sent to Jev`,
|
|
129
|
+
};
|
|
84
130
|
const object = await exec("git", [
|
|
85
131
|
"rev-parse",
|
|
86
132
|
"--verify",
|
|
@@ -117,10 +163,20 @@ export async function collectAskRepository(exec, cwd, base, signal) {
|
|
|
117
163
|
ok: false,
|
|
118
164
|
error: `before evidence unavailable for ${path}`,
|
|
119
165
|
};
|
|
166
|
+
if (loaded.stdout.includes("\0"))
|
|
167
|
+
return {
|
|
168
|
+
ok: false,
|
|
169
|
+
cause: "binary_or_non_utf8",
|
|
170
|
+
error: `${path}: binary content: not sent to Jev`,
|
|
171
|
+
};
|
|
120
172
|
const decoded = verifyGitUtf8(loaded.stdout, id, size);
|
|
121
173
|
return decoded.ok
|
|
122
174
|
? decoded
|
|
123
|
-
: {
|
|
175
|
+
: {
|
|
176
|
+
ok: false,
|
|
177
|
+
cause: "binary_or_non_utf8",
|
|
178
|
+
error: `${path}: ${decoded.error}`,
|
|
179
|
+
};
|
|
124
180
|
}
|
|
125
181
|
catch (error) {
|
|
126
182
|
return { ok: false, error: String(error) };
|
package/dist/adapters/command.js
CHANGED
|
@@ -5,8 +5,39 @@ import { ASK_TIMEOUT_S, OUTPUT_CHUNK_CHARS, OUTPUT_FAILURE_WINDOW_LINES, OUTPUT_
|
|
|
5
5
|
import { cleanOutput, createRarityCompressor, failureTargets, lineShape, } from "../core/command-output.js";
|
|
6
6
|
import { outputLines } from "./output-lines.js";
|
|
7
7
|
import { resolveShell } from "./shell.js";
|
|
8
|
-
|
|
8
|
+
const TRUNCATION_MARK = `…[line truncated at ${OUTPUT_LINE_MAX_CHARS} chars]`;
|
|
9
|
+
/** Shortest key prefix worth redacting when a line cut leaves only its start. */
|
|
10
|
+
const SECRET_PREFIX_MIN = 4;
|
|
11
|
+
/**
|
|
12
|
+
* Replace every occurrence of `secret` in `text`, counting replacements. A
|
|
13
|
+
* line cut at OUTPUT_LINE_MAX_CHARS can end inside the key; that trailing key
|
|
14
|
+
* prefix is redacted too.
|
|
15
|
+
*/
|
|
16
|
+
export function redactSecret(text, secret) {
|
|
17
|
+
if (!secret)
|
|
18
|
+
return { text, count: 0 };
|
|
19
|
+
const parts = text.split(secret);
|
|
20
|
+
let result = parts.join("[redacted]");
|
|
21
|
+
let count = parts.length - 1;
|
|
22
|
+
if (result.endsWith(TRUNCATION_MARK)) {
|
|
23
|
+
const kept = result.slice(0, -TRUNCATION_MARK.length);
|
|
24
|
+
const min = Math.min(SECRET_PREFIX_MIN, secret.length);
|
|
25
|
+
for (let length = Math.min(secret.length - 1, kept.length); length >= min; length--) {
|
|
26
|
+
if (kept.endsWith(secret.slice(0, length))) {
|
|
27
|
+
result = `${kept.slice(0, -length)}[redacted]${TRUNCATION_MARK}`;
|
|
28
|
+
count++;
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return { text: result, count };
|
|
34
|
+
}
|
|
35
|
+
export async function captureCommand(exec, cwd, command, timeoutS = ASK_TIMEOUT_S, signal,
|
|
36
|
+
/** Configured Jev API key, replaced in the captured command and output. */
|
|
37
|
+
secret) {
|
|
9
38
|
const directory = await mkdtemp(join(tmpdir(), "jev-output-"));
|
|
39
|
+
let commandExecution = "not_started";
|
|
40
|
+
let completion;
|
|
10
41
|
try {
|
|
11
42
|
await chmod(directory, 0o700);
|
|
12
43
|
const stdoutPath = join(directory, "stdout");
|
|
@@ -17,7 +48,13 @@ export async function captureCommand(exec, cwd, command, timeoutS = ASK_TIMEOUT_
|
|
|
17
48
|
const shell = resolveShell();
|
|
18
49
|
// Fail closed: never spawn a bare name that PATH could resolve to WSL.
|
|
19
50
|
if (!shell.ok)
|
|
20
|
-
|
|
51
|
+
return {
|
|
52
|
+
ok: false,
|
|
53
|
+
cause: "file_unavailable",
|
|
54
|
+
commandExecution: "not_started",
|
|
55
|
+
error: shell.error,
|
|
56
|
+
};
|
|
57
|
+
commandExecution = "unknown";
|
|
21
58
|
executed = await exec(shell.executable, [
|
|
22
59
|
...shell.prefix,
|
|
23
60
|
"-c",
|
|
@@ -26,27 +63,19 @@ export async function captureCommand(exec, cwd, command, timeoutS = ASK_TIMEOUT_
|
|
|
26
63
|
stdoutPath,
|
|
27
64
|
stderrPath,
|
|
28
65
|
], { cwd, timeout: timeoutS * 1000, signal });
|
|
66
|
+
commandExecution = "finished";
|
|
67
|
+
completion = {
|
|
68
|
+
commandExitCode: executed.killed ? null : executed.code,
|
|
69
|
+
commandTimedOut: executed.killed,
|
|
70
|
+
};
|
|
29
71
|
}
|
|
30
72
|
catch (error) {
|
|
31
73
|
signal?.throwIfAborted();
|
|
32
74
|
return {
|
|
33
|
-
ok:
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
timed_out: false,
|
|
38
|
-
stdout: "",
|
|
39
|
-
stderr: `Command executable unavailable: ${String(error)}`,
|
|
40
|
-
compressed: true,
|
|
41
|
-
truncated: false,
|
|
42
|
-
},
|
|
43
|
-
originalBytes: 0,
|
|
44
|
-
compressedChars: 0,
|
|
45
|
-
lineOmittedChars: 0,
|
|
46
|
-
shapeLimitExceeded: false,
|
|
47
|
-
selectedPassages: false,
|
|
48
|
-
assertion: false,
|
|
49
|
-
targets: [],
|
|
75
|
+
ok: false,
|
|
76
|
+
cause: "file_unavailable",
|
|
77
|
+
commandExecution,
|
|
78
|
+
error: `Command executable unavailable: ${String(error)}`,
|
|
50
79
|
};
|
|
51
80
|
}
|
|
52
81
|
signal?.throwIfAborted();
|
|
@@ -65,6 +94,9 @@ export async function captureCommand(exec, cwd, command, timeoutS = ASK_TIMEOUT_
|
|
|
65
94
|
if (sizes.some((size) => size > OUTPUT_FILE_MAX_BYTES))
|
|
66
95
|
return {
|
|
67
96
|
ok: false,
|
|
97
|
+
cause: "evidence_too_large",
|
|
98
|
+
commandExecution: "finished",
|
|
99
|
+
...completion,
|
|
68
100
|
error: `output exceeded ${OUTPUT_FILE_MAX_BYTES} bytes per stream; narrow command`,
|
|
69
101
|
};
|
|
70
102
|
const targets = [];
|
|
@@ -74,17 +106,23 @@ export async function captureCommand(exec, cwd, command, timeoutS = ASK_TIMEOUT_
|
|
|
74
106
|
let lineOmittedChars = 0;
|
|
75
107
|
let shapeLimitExceeded = false;
|
|
76
108
|
const texts = [];
|
|
109
|
+
let redactions = 0;
|
|
110
|
+
const redacted = (text) => {
|
|
111
|
+
const result = redactSecret(text, secret);
|
|
112
|
+
redactions += result.count;
|
|
113
|
+
return result.text;
|
|
114
|
+
};
|
|
77
115
|
for (const [index, path] of [stdoutPath, stderrPath].entries()) {
|
|
78
116
|
if (!sizes[index]) {
|
|
79
|
-
texts.push(index === 0 ? executed.stdout : executed.stderr);
|
|
117
|
+
texts.push(redacted(index === 0 ? executed.stdout : executed.stderr));
|
|
80
118
|
continue;
|
|
81
119
|
}
|
|
82
120
|
const frequencies = new Map();
|
|
83
121
|
let shapeLimit = false;
|
|
84
122
|
for await (const raw of outputLines(path, signal)) {
|
|
85
123
|
signal?.throwIfAborted();
|
|
86
|
-
const line = cleanOutput(raw);
|
|
87
|
-
linesTruncated ||= raw.endsWith(
|
|
124
|
+
const line = redactSecret(cleanOutput(raw), secret).text;
|
|
125
|
+
linesTruncated ||= raw.endsWith(TRUNCATION_MARK);
|
|
88
126
|
const shape = lineShape(line);
|
|
89
127
|
if (!frequencies.has(shape) &&
|
|
90
128
|
frequencies.size >= OUTPUT_SHAPE_MAX_COUNT) {
|
|
@@ -122,17 +160,19 @@ export async function captureCommand(exec, cwd, command, timeoutS = ASK_TIMEOUT_
|
|
|
122
160
|
lineOmittedChars += chars;
|
|
123
161
|
})) {
|
|
124
162
|
signal?.throwIfAborted();
|
|
125
|
-
linesTruncated ||= raw.endsWith(
|
|
126
|
-
|
|
163
|
+
linesTruncated ||= raw.endsWith(TRUNCATION_MARK);
|
|
164
|
+
// Redact before compression, failure windows and state assembly.
|
|
165
|
+
const line = redacted(cleanOutput(raw));
|
|
166
|
+
compressor.line(line);
|
|
127
167
|
if (!failureSeen) {
|
|
128
|
-
failureWindow.push(
|
|
168
|
+
failureWindow.push(line);
|
|
129
169
|
if (failureWindow.length > OUTPUT_FAILURE_WINDOW_LINES + 1)
|
|
130
170
|
failureWindow.shift();
|
|
131
171
|
if (isAnchor(raw))
|
|
132
172
|
failureSeen = true;
|
|
133
173
|
}
|
|
134
174
|
else if (afterFailure++ < OUTPUT_FAILURE_WINDOW_LINES) {
|
|
135
|
-
failureWindow.push(
|
|
175
|
+
failureWindow.push(line);
|
|
136
176
|
if (!secondSeen && afterFailure > 1 && isFailingTestsHeader(raw)) {
|
|
137
177
|
secondSeen = true;
|
|
138
178
|
afterSecond = 0;
|
|
@@ -142,11 +182,11 @@ export async function captureCommand(exec, cwd, command, timeoutS = ASK_TIMEOUT_
|
|
|
142
182
|
if (isFailingTestsHeader(raw)) {
|
|
143
183
|
secondSeen = true;
|
|
144
184
|
afterSecond = 0;
|
|
145
|
-
failureWindow.push(
|
|
185
|
+
failureWindow.push(line);
|
|
146
186
|
}
|
|
147
187
|
}
|
|
148
188
|
else if (afterSecond++ < OUTPUT_FAILURE_WINDOW_LINES) {
|
|
149
|
-
failureWindow.push(
|
|
189
|
+
failureWindow.push(line);
|
|
150
190
|
}
|
|
151
191
|
}
|
|
152
192
|
compressor.finish();
|
|
@@ -157,8 +197,10 @@ export async function captureCommand(exec, cwd, command, timeoutS = ASK_TIMEOUT_
|
|
|
157
197
|
}
|
|
158
198
|
return {
|
|
159
199
|
ok: true,
|
|
200
|
+
commandExecution: "finished",
|
|
201
|
+
...completion,
|
|
160
202
|
output: {
|
|
161
|
-
command,
|
|
203
|
+
command: redacted(command),
|
|
162
204
|
exit_code: executed.killed ? null : executed.code,
|
|
163
205
|
timed_out: executed.killed,
|
|
164
206
|
stdout: texts[0] ?? "",
|
|
@@ -173,6 +215,17 @@ export async function captureCommand(exec, cwd, command, timeoutS = ASK_TIMEOUT_
|
|
|
173
215
|
selectedPassages: false,
|
|
174
216
|
assertion: signature === "assertion",
|
|
175
217
|
targets,
|
|
218
|
+
redactions,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
catch (error) {
|
|
222
|
+
signal?.throwIfAborted();
|
|
223
|
+
return {
|
|
224
|
+
ok: false,
|
|
225
|
+
cause: "file_unavailable",
|
|
226
|
+
commandExecution,
|
|
227
|
+
...completion,
|
|
228
|
+
error: `Command capture unavailable: ${String(error)}`,
|
|
176
229
|
};
|
|
177
230
|
}
|
|
178
231
|
finally {
|
package/dist/adapters/docs.js
CHANGED
|
@@ -12,6 +12,7 @@ export async function collectDocsInventory(exec, cwd, signal) {
|
|
|
12
12
|
if (root.code || root.killed)
|
|
13
13
|
return {
|
|
14
14
|
ok: false,
|
|
15
|
+
cause: signal?.aborted ? "cancelled" : "git_failure",
|
|
15
16
|
error: root.stderr.trim() || "Repository root not found.",
|
|
16
17
|
};
|
|
17
18
|
cwd = root.stdout.trim();
|
|
@@ -23,6 +24,7 @@ export async function collectDocsInventory(exec, cwd, signal) {
|
|
|
23
24
|
if (list.code || list.killed)
|
|
24
25
|
return {
|
|
25
26
|
ok: false,
|
|
27
|
+
cause: signal?.aborted ? "cancelled" : "git_failure",
|
|
26
28
|
error: list.stderr.trim() || "Unable to inventory tracked files.",
|
|
27
29
|
};
|
|
28
30
|
const tracked = new Set(list.stdout.split("\0").filter(Boolean));
|
|
@@ -46,7 +48,11 @@ export async function collectDocsInventory(exec, cwd, signal) {
|
|
|
46
48
|
inventory: tracked,
|
|
47
49
|
});
|
|
48
50
|
if (!opened.ok) {
|
|
49
|
-
limits.push({
|
|
51
|
+
limits.push({
|
|
52
|
+
path,
|
|
53
|
+
reason: opened.error,
|
|
54
|
+
...(opened.cause ? { cause: opened.cause } : {}),
|
|
55
|
+
});
|
|
50
56
|
return undefined;
|
|
51
57
|
}
|
|
52
58
|
const handle = opened.handle;
|
|
@@ -130,10 +136,17 @@ export function docsDeclarationSearch(exec, inventory, signal) {
|
|
|
130
136
|
if (path === undefined)
|
|
131
137
|
return;
|
|
132
138
|
const location = await resolveInsideRepo(inventory.cwd, path);
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
139
|
+
const admission = location.ok
|
|
140
|
+
? await checkFileAdmission(inventory.cwd, location.rel, exec, signal, inventory.tracked)
|
|
141
|
+
: location;
|
|
142
|
+
if (admission.ok)
|
|
136
143
|
paths.push(path);
|
|
144
|
+
else if (admission.cause === "secret_pattern")
|
|
145
|
+
inventory.limits.push({
|
|
146
|
+
path,
|
|
147
|
+
reason: admission.error,
|
|
148
|
+
cause: admission.cause,
|
|
149
|
+
});
|
|
137
150
|
}
|
|
138
151
|
}));
|
|
139
152
|
return paths;
|
|
@@ -142,12 +155,19 @@ export function docsDeclarationSearch(exec, inventory, signal) {
|
|
|
142
155
|
if (!paths.length)
|
|
143
156
|
return new Map();
|
|
144
157
|
const patterns = ["-e", docsDeclarationPattern(names)];
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
158
|
+
let result;
|
|
159
|
+
try {
|
|
160
|
+
result = await exec("rg", ["--json", ...patterns, "--", ...paths], {
|
|
161
|
+
cwd: inventory.cwd,
|
|
162
|
+
timeout: TIMEOUT_MS,
|
|
163
|
+
signal,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
// A missing or unstartable rg is an unavailable search, not a failure.
|
|
168
|
+
signal?.throwIfAborted();
|
|
169
|
+
}
|
|
170
|
+
if (!result || result.killed || (result.code !== 0 && result.code !== 1)) {
|
|
151
171
|
inventory.limits.push({
|
|
152
172
|
path: "documentation",
|
|
153
173
|
reason: "declaration search unavailable",
|