jev-affected 0.1.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/.env.example +3 -0
- package/CHANGELOG.md +8 -0
- package/CODE_OF_CONDUCT.md +7 -0
- package/CONTRIBUTING.md +16 -0
- package/EVALUATION.md +77 -0
- package/LICENSE +21 -0
- package/NOTICE +6 -0
- package/README.md +201 -0
- package/SECURITY.md +21 -0
- package/assets/README.md +5 -0
- package/assets/demo.gif +0 -0
- package/assets/demo.mp4 +0 -0
- package/assets/favicon.svg +1 -0
- package/assets/logo-dark.svg +1 -0
- package/assets/logo-mark.svg +1 -0
- package/assets/logo-wordmark.svg +1 -0
- package/assets/logo.svg +1 -0
- package/assets/social-card.png +0 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +214 -0
- package/dist/config.d.ts +41 -0
- package/dist/config.js +97 -0
- package/dist/eval.d.ts +41 -0
- package/dist/eval.js +96 -0
- package/dist/executor.d.ts +6 -0
- package/dist/executor.js +68 -0
- package/dist/git.d.ts +21 -0
- package/dist/git.js +102 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/planner.d.ts +37 -0
- package/dist/planner.js +132 -0
- package/dist/provider.d.ts +21 -0
- package/dist/provider.js +28 -0
- package/evals/fixtures/api-response-field.json +53 -0
- package/evals/fixtures/auth-session-ttl.json +53 -0
- package/evals/fixtures/comment-only.json +53 -0
- package/evals/fixtures/db-schema.json +53 -0
- package/evals/fixtures/docs-only.json +53 -0
- package/evals/fixtures/logging-only.json +53 -0
- package/evals/fixtures/performance.json +53 -0
- package/examples/basic/jev-affected.yml +16 -0
- package/package.json +84 -0
- package/skills/jev-affected/SKILL.md +51 -0
- package/skills/jev-affected/agents/openai.yaml +7 -0
- package/skills/jev-affected/assets/icon-large.svg +4 -0
- package/skills/jev-affected/assets/icon-small.svg +4 -0
package/.env.example
ADDED
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0
|
|
4
|
+
|
|
5
|
+
- YAML semantic task conditions, deterministic globs and protected tasks.
|
|
6
|
+
- Jev Noul analysis, inspectable plans, JSON output and safe fallback.
|
|
7
|
+
- CLI initialization, execution, diagnostics and fixture evaluation.
|
|
8
|
+
- Pinned-model cache, privacy exclusions, offline tests and CI matrix.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Code of conduct
|
|
2
|
+
|
|
3
|
+
We welcome contributors of every background and experience level. Be respectful, give constructive feedback, and focus criticism on work rather than people.
|
|
4
|
+
|
|
5
|
+
Harassment, discrimination, threats, sexualized conduct and disclosure of private information are unacceptable. Maintainers may remove content or restrict participation for violations, proportionately and with an explanation when appropriate.
|
|
6
|
+
|
|
7
|
+
Report concerns privately to the repository maintainers through an established channel. Maintainers will protect reporter privacy and avoid retaliation. This policy applies in project spaces and when representing the project publicly.
|
package/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Use Node.js 20+ and pnpm 10.11.0.
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
pnpm install
|
|
7
|
+
pnpm typecheck
|
|
8
|
+
pnpm lint
|
|
9
|
+
pnpm test
|
|
10
|
+
pnpm build
|
|
11
|
+
pnpm smoke
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Include tests for routing, privacy and execution behavior. Prefer offline fixtures; identify synthetic responses explicitly and never present them as live model measurements. Live evaluation is optional locally and mandatory before release. Never submit credentials or private code.
|
|
15
|
+
|
|
16
|
+
Use `pnpm exec changeset` for user-visible changes. Keep changes within the v0.1 CLI scope. Follow CODE_OF_CONDUCT.md.
|
package/EVALUATION.md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Evaluation
|
|
2
|
+
|
|
3
|
+
`jev-affected` is designed around one primary safety risk: skipping a task that a
|
|
4
|
+
change actually requires. The evaluation suite therefore treats every false skip
|
|
5
|
+
as a gate failure.
|
|
6
|
+
|
|
7
|
+
## Method
|
|
8
|
+
|
|
9
|
+
Seven curated Git-diff fixtures cover session lifetime changes, public API fields,
|
|
10
|
+
comments, logging, database schema, performance-sensitive code, and documentation.
|
|
11
|
+
Each fixture declares the tasks that must run. The evaluator asks Jev the same
|
|
12
|
+
questions used by the planner, applies the configured `skipBelow` threshold, and
|
|
13
|
+
compares the resulting plan with those expectations.
|
|
14
|
+
|
|
15
|
+
The live evaluation passes only when all of these conditions hold:
|
|
16
|
+
|
|
17
|
+
- false skips: `0`
|
|
18
|
+
- invalid or missing model answers: `0`
|
|
19
|
+
- fixture cases with at least one skipped task: `>= 2`
|
|
20
|
+
- overall task reduction: `>= 25%`
|
|
21
|
+
|
|
22
|
+
Provider errors remain fail-safe in ordinary planning: affected candidate tasks
|
|
23
|
+
run. A live evaluation instead fails on invalid answers so it can expose provider
|
|
24
|
+
or integration problems.
|
|
25
|
+
|
|
26
|
+
## Latest live result
|
|
27
|
+
|
|
28
|
+
Run on 2026-09-21 with `jev-1.13.0` and `skipBelow: 0.10`:
|
|
29
|
+
|
|
30
|
+
| Metric | Result |
|
|
31
|
+
| --- | ---: |
|
|
32
|
+
| Cases | 7 |
|
|
33
|
+
| Task decisions | 28 |
|
|
34
|
+
| False skips | 0 |
|
|
35
|
+
| Invalid cases | 0 |
|
|
36
|
+
| Cases with reduction | 6 |
|
|
37
|
+
| Task reduction | 46.43% |
|
|
38
|
+
| Unnecessary-run rate | 45.83% |
|
|
39
|
+
| API calls | 7 |
|
|
40
|
+
| Total latency | 2,139 ms |
|
|
41
|
+
| Input tokens | 2,882 |
|
|
42
|
+
| Output tokens | 476 |
|
|
43
|
+
|
|
44
|
+
Selected tasks by fixture:
|
|
45
|
+
|
|
46
|
+
| Fixture | Selected |
|
|
47
|
+
| --- | ---: |
|
|
48
|
+
| API field | 3 / 4 |
|
|
49
|
+
| Authentication | 4 / 4 |
|
|
50
|
+
| Comment | 1 / 4 |
|
|
51
|
+
| Database schema | 3 / 4 |
|
|
52
|
+
| Documentation | 0 / 4 |
|
|
53
|
+
| Logging | 1 / 4 |
|
|
54
|
+
| Performance | 3 / 4 |
|
|
55
|
+
|
|
56
|
+
## Reproduce
|
|
57
|
+
|
|
58
|
+
Build first, then run either evaluation:
|
|
59
|
+
|
|
60
|
+
```sh
|
|
61
|
+
pnpm build
|
|
62
|
+
pnpm eval
|
|
63
|
+
pnpm eval:live
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
`pnpm eval` uses deterministic synthetic answers and verifies evaluation and
|
|
67
|
+
decision plumbing without making a network request. `pnpm eval:live` reads
|
|
68
|
+
`TYPESAFE_API_KEY` from the process environment or a local `.env` file and sends
|
|
69
|
+
the fixture inputs to Jev.
|
|
70
|
+
|
|
71
|
+
## Limits
|
|
72
|
+
|
|
73
|
+
The fixtures are small, curated, and specific to this task taxonomy. The latest
|
|
74
|
+
result is evidence for this release and threshold; it is not a general accuracy
|
|
75
|
+
claim. Live model results can vary between versions and runs. Repositories should
|
|
76
|
+
protect mandatory checks with `always: true` or `allowSkip: false`, keep conditions
|
|
77
|
+
specific, and maintain their own representative evaluation cases.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 jammaru
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/NOTICE
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
<p align="center"><img src="assets/logo-wordmark.svg" width="480" alt="jev-affected"></p>
|
|
2
|
+
|
|
3
|
+
<p align="center"><strong>Semantic task routing for software development.</strong></p>
|
|
4
|
+
<p align="center">Run tasks based on what changed, not where it changed.</p>
|
|
5
|
+
<p align="center">v0.1.0 Public Beta · Node.js 20+ · TypeScript · MIT · Jev-powered</p>
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
Run the reproducible offline demonstration with `pnpm demo` after building. [MP4 version](assets/demo.mp4).
|
|
10
|
+
|
|
11
|
+
## What is jev-affected?
|
|
12
|
+
|
|
13
|
+
Define the conditions under which tasks matter in natural language. Jev evaluates those conditions against a Git diff. Code turns the probabilities into a safe, inspectable task plan for your existing test runner, build system, CI, or coding agent.
|
|
14
|
+
|
|
15
|
+
## Why?
|
|
16
|
+
|
|
17
|
+
A session lifetime change can affect login behavior without changing the public API. File dependencies alone do not express that distinction. Semantic conditions supplement your existing dependency graph.
|
|
18
|
+
|
|
19
|
+
## Quick start
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
pnpm add -D jev-affected
|
|
23
|
+
pnpm exec jev-affected init
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
With npm:
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
npm install -D jev-affected
|
|
30
|
+
npx jev-affected init
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Create `.env` in the repository root. The CLI loads it automatically:
|
|
34
|
+
|
|
35
|
+
```dotenv
|
|
36
|
+
TYPESAFE_API_KEY=your_api_key_here
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
`.env` is ignored by this repository's generated `.gitignore`. Never put the key in `jev-affected.yml` or commit it. An existing process environment value takes precedence over `.env`. You can also set it for the current shell:
|
|
40
|
+
|
|
41
|
+
```powershell
|
|
42
|
+
$env:TYPESAFE_API_KEY = "your_api_key_here"
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
export TYPESAFE_API_KEY="your_api_key_here"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Configure your own trusted commands, commit your changes on a feature branch, then:
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
npx jev-affected inspect --base main
|
|
53
|
+
npx jev-affected plan --base main
|
|
54
|
+
npx jev-affected run --base main
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
`plan` performs analysis but never executes task commands. `run` executes configured commands. **Only committed changes are included**: default head is `HEAD`, not the working tree. Commit agent edits before planning.
|
|
58
|
+
|
|
59
|
+
## How it works
|
|
60
|
+
|
|
61
|
+
```mermaid
|
|
62
|
+
flowchart TD
|
|
63
|
+
A[Git diff] --> B[Deterministic candidate tasks]
|
|
64
|
+
B --> C[Jev Noul questions]
|
|
65
|
+
C --> D[Semantic task plan]
|
|
66
|
+
D --> E[Tests]
|
|
67
|
+
D --> F[Build]
|
|
68
|
+
D --> G[Code generation]
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Jev supplies `P(condition = true)`. A task is skipped only when its valid probability is **strictly below** `skipBelow`. Equality means RUN. No model-generated commands or explanations are used.
|
|
72
|
+
|
|
73
|
+
## Configuration
|
|
74
|
+
|
|
75
|
+
Only YAML is supported. Unknown keys and unsafe policies are rejected.
|
|
76
|
+
|
|
77
|
+
```yaml
|
|
78
|
+
version: 1
|
|
79
|
+
model: jev-latest
|
|
80
|
+
defaults:
|
|
81
|
+
skipBelow: 0.10
|
|
82
|
+
policy:
|
|
83
|
+
uncertain: run
|
|
84
|
+
onError: run
|
|
85
|
+
analysis:
|
|
86
|
+
maxDiffBytes: 100000
|
|
87
|
+
timeoutMs: 10000
|
|
88
|
+
exclude: ["**/*.pem", "**/*.key"]
|
|
89
|
+
execution:
|
|
90
|
+
parallel: false
|
|
91
|
+
concurrency: 4
|
|
92
|
+
tasks:
|
|
93
|
+
auth-e2e:
|
|
94
|
+
command: npm run test:auth
|
|
95
|
+
when: Could authentication, sessions, cookies or login behavior change?
|
|
96
|
+
sdk:
|
|
97
|
+
command: npm run generate:sdk
|
|
98
|
+
when: Could the public API contract change?
|
|
99
|
+
typecheck:
|
|
100
|
+
command: npm run typecheck
|
|
101
|
+
always: true
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
`threshold` is a compatibility alias for `skipBelow`; specifying both in the same scope is an error. Task values override defaults. `allowSkip: false` protects a task just like `always: true`.
|
|
105
|
+
|
|
106
|
+
Optional task `include` globs select candidates; task `ignore` and global `ignore` remove matches. Both rename paths are checked. **These are hard dependency rules supplied by you**: overly narrow globs can miss required tasks. Protected tasks bypass them. See [examples/basic](examples/basic/jev-affected.yml).
|
|
107
|
+
|
|
108
|
+
## Safety
|
|
109
|
+
|
|
110
|
+
- Missing credentials, timeout, provider error, missing actual model version or invalid probability → RUN for semantic candidates.
|
|
111
|
+
- Sensitive file changes, unreadable/oversized patches, binary changes or submodules → RUN all tasks, with no semantic request.
|
|
112
|
+
- No matching changes → SKIP, except protected tasks.
|
|
113
|
+
- Task commands come exclusively from your configuration and run through your platform shell. Only run configurations you trust.
|
|
114
|
+
|
|
115
|
+
Low model probabilities are estimates, not a guarantee that a task is unnecessary. Use protected tasks for checks that must always run.
|
|
116
|
+
|
|
117
|
+
## CLI
|
|
118
|
+
|
|
119
|
+
| Command | Purpose |
|
|
120
|
+
| --- | --- |
|
|
121
|
+
| `init` | Create config without overwriting an existing file |
|
|
122
|
+
| `plan [--json]` | Produce a plan; never execute tasks |
|
|
123
|
+
| `run [--parallel] [--concurrency 4]` | Execute RUN tasks; sequential by default |
|
|
124
|
+
| `why <task>` | Show condition, probability, threshold, files and model |
|
|
125
|
+
| `inspect` | Preview sanitized analysis inputs locally; no request |
|
|
126
|
+
| `doctor` | Check Node, config, Git base, API key and models endpoint |
|
|
127
|
+
| `eval [--live]` | Evaluate fixture decisions and false-skip rate |
|
|
128
|
+
|
|
129
|
+
Common flags: `--base`, `--head`, `--config`, `--no-cache`, `--json`, `--help`, `--version`. JSON task output includes a stable `version: 1`. During `run --json`, child output goes to stderr, keeping stdout parseable.
|
|
130
|
+
|
|
131
|
+
Exit codes: `0` success; `1` failed executed task or failed evaluation gate; `2` config/argument/base or doctor check error; `3` internal/I/O failure. API failures do not fail a normal plan or run by themselves.
|
|
132
|
+
|
|
133
|
+
Base priority: CLI → config → GitHub PR base branch → `origin/main` → `main` → `master`. The comparison starts at the merge-base with head. Explicit invalid bases fail instead of silently choosing another branch. Fetch full history in CI.
|
|
134
|
+
|
|
135
|
+
## CI
|
|
136
|
+
|
|
137
|
+
```yaml
|
|
138
|
+
- uses: actions/checkout@v4
|
|
139
|
+
with:
|
|
140
|
+
fetch-depth: 0
|
|
141
|
+
- run: npx jev-affected run --base origin/main
|
|
142
|
+
env:
|
|
143
|
+
TYPESAFE_API_KEY: ${{ secrets.TYPESAFE_API_KEY }}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Install dependencies first. Do not expose credentials to untrusted PR code. Nx, Turbo and other runners can be invoked as configured commands; no plugins are required.
|
|
147
|
+
|
|
148
|
+
## Agents
|
|
149
|
+
|
|
150
|
+
After committing edits, run `jev-affected plan --json` and execute all tasks whose `decision` is `run`, or use `jev-affected run`. `inspect` lets you review what could leave the repository first.
|
|
151
|
+
|
|
152
|
+
The package includes a reusable agent skill at [`skills/jev-affected`](skills/jev-affected). Point a compatible coding agent at that directory, or copy it into the agent's skills directory, to give it the safe planning and execution workflow.
|
|
153
|
+
|
|
154
|
+
## Architecture and library API
|
|
155
|
+
|
|
156
|
+
Modules separate configuration, Git collection, provider access, planning, execution and reporting. Jev is the only supported production provider. The provider interface supports offline test doubles.
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
import { loadConfig, createPlan } from 'jev-affected';
|
|
160
|
+
const config = await loadConfig('jev-affected.yml');
|
|
161
|
+
const plan = await createPlan({ config, base: 'main' });
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Automatic caches store probabilities and model metadata in `.git/jev-affected/cache` (including Git worktree support). Keys include state, questions, configuration and pinned actual model version. Corrupt cache entries fall back to RUN. **Moving aliases such as `jev-latest` bypass caching**, because an alias cannot safely establish its current actual version without another request. Pin an actual `jev-x.y.z` version to enable reuse. Neither patches nor API keys are stored in cache payloads. Cache writes are the only local side effect of `plan`; `--no-cache` disables them.
|
|
165
|
+
|
|
166
|
+
## Evaluation
|
|
167
|
+
|
|
168
|
+
```sh
|
|
169
|
+
pnpm eval
|
|
170
|
+
pnpm eval:live
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Seven fixtures cover session TTL, API fields, comments, logging, schema, performance and docs. Offline responses are **synthetic, not recorded model answers**. Zero false skips on those responses verifies decision plumbing, not Jev accuracy. Live evaluation fails unless it receives valid model answers, produces zero false skips, reduces tasks in at least two cases, and achieves at least 25% task reduction. It prints the actual model, task probabilities, latency and token usage. Cost is `null` because no pricing assumptions are embedded.
|
|
174
|
+
|
|
175
|
+
See [EVALUATION.md](EVALUATION.md) for the methodology, release gates, latest live result and limitations.
|
|
176
|
+
|
|
177
|
+
## Privacy
|
|
178
|
+
|
|
179
|
+
No backend and no telemetry. Data is read locally; semantic inputs go directly to TypeSafe through its SDK. By default, `.env*`, PEM/key files, and names containing `credentials`, `secret` or `token` are withheld. Custom exclusions add to these defaults. Filename exclusions cannot find every secret embedded in arbitrary source files: inspect inputs and use appropriate repository practices. SDK logging is explicitly disabled.
|
|
180
|
+
|
|
181
|
+
`TYPESAFE_BASE_URL` is honored by the official SDK; inspect this environment setting before use. Review [TypeSafe's data-processing terms](https://typesafe.ai/legal/mca) for your organization.
|
|
182
|
+
|
|
183
|
+
## FAQ
|
|
184
|
+
|
|
185
|
+
**Does this replace my build system?** No. It produces a plan and invokes your commands.
|
|
186
|
+
|
|
187
|
+
**Does `why` generate an explanation?** No. It displays decision evidence. With a moving model alias it can make a fresh request; it is not a historical plan lookup.
|
|
188
|
+
|
|
189
|
+
**What if Jev is down?** Candidate tasks run. Deterministic rules and protected tasks still apply.
|
|
190
|
+
|
|
191
|
+
**Are uncommitted files included?** No, v0.1 compares commits. Explicit refs make the input reproducible.
|
|
192
|
+
|
|
193
|
+
## Roadmap
|
|
194
|
+
|
|
195
|
+
v0.1 focuses on inspectable semantic dependencies and safe task plans. Later candidates: working-tree mode, historical evaluations, watch mode, task groups and Nx/Turbo adapters. No GUI, SaaS, MCP server or autonomous command generation is included.
|
|
196
|
+
|
|
197
|
+
## Contributing and license
|
|
198
|
+
|
|
199
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md), [SECURITY.md](SECURITY.md), and the [MIT LICENSE](LICENSE).
|
|
200
|
+
|
|
201
|
+
jev-affected is an independent open-source project and is not affiliated with or endorsed by TypeSafe AI.
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Security
|
|
2
|
+
|
|
3
|
+
## Supported versions
|
|
4
|
+
|
|
5
|
+
The current 0.1.x line receives security fixes.
|
|
6
|
+
|
|
7
|
+
## Reporting a vulnerability
|
|
8
|
+
|
|
9
|
+
Do not post secrets or exploit details in a public issue. Once the public repository is configured, use its GitHub private vulnerability reporting feature. Until then, contact the repository owner privately using an established channel. Enabling private reporting is a release gate.
|
|
10
|
+
|
|
11
|
+
## Secret handling
|
|
12
|
+
|
|
13
|
+
Set TYPESAFE_API_KEY through your environment or CI secret store. The application disables SDK logging and does not emit provider error bodies. Exclusion globs are additive; they cannot detect every embedded secret. Inspect your inputs before enabling remote analysis.
|
|
14
|
+
|
|
15
|
+
## External API calls
|
|
16
|
+
|
|
17
|
+
Diffs, changed paths, commit identifiers and questions are sent to the TypeSafe SDK endpoint. `inspect` is local. `doctor` contacts the models endpoint without uploading repository data. `eval --live` uploads fixture states. No telemetry is collected.
|
|
18
|
+
|
|
19
|
+
## Execution trust
|
|
20
|
+
|
|
21
|
+
Commands use a shell. Treat configuration as executable code. Do not run commands from untrusted checkouts with credentials or elevated privileges. The model never supplies commands. Local cache contents are trusted like the checkout itself.
|
package/assets/README.md
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Brand assets
|
|
2
|
+
|
|
3
|
+
Original geometric graph mark. Black and white variants are monochrome; the lime accent is reserved for supporting material. SVGs are the editable sources. `scripts/brand.py` regenerates marks and the 1200×630 social card (Pillow required).
|
|
4
|
+
|
|
5
|
+
`demo.gif` and `demo.mp4` are a 20-second illustrative offline fixture animation. They are explicitly not live Jev output. Reproduce their decision content with `pnpm demo`.
|
package/assets/demo.gif
ADDED
|
Binary file
|
package/assets/demo.mp4
ADDED
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="jev-affected semantic graph"><g fill="none" stroke="#161a17" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"><path opacity=".25" d="M32 10L10 35M32 10L54 35"/><path d="M32 10V54"/></g><g fill="#161a17"><circle cx="32" cy="10" r="6"/><circle cx="32" cy="35" r="6"/><circle cx="32" cy="54" r="6"/><circle opacity=".25" cx="10" cy="35" r="5"/><circle opacity=".25" cx="54" cy="35" r="5"/></g></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="jev-affected semantic graph"><g fill="none" stroke="#f1f5f0" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"><path opacity=".25" d="M32 10L10 35M32 10L54 35"/><path d="M32 10V54"/></g><g fill="#f1f5f0"><circle cx="32" cy="10" r="6"/><circle cx="32" cy="35" r="6"/><circle cx="32" cy="54" r="6"/><circle opacity=".25" cx="10" cy="35" r="5"/><circle opacity=".25" cx="54" cy="35" r="5"/></g></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="jev-affected semantic graph"><g fill="none" stroke="#161a17" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"><path opacity=".25" d="M32 10L10 35M32 10L54 35"/><path d="M32 10V54"/></g><g fill="#161a17"><circle cx="32" cy="10" r="6"/><circle cx="32" cy="35" r="6"/><circle cx="32" cy="54" r="6"/><circle opacity=".25" cx="10" cy="35" r="5"/><circle opacity=".25" cx="54" cy="35" r="5"/></g></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 560 96" role="img" aria-label="jev-affected"><rect width="560" height="96" rx="12" fill="#101713"/><g transform="translate(20 16)"><g fill="none" stroke="#f1f5f0" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"><path opacity=".25" d="M32 10L10 35M32 10L54 35"/><path d="M32 10V54"/></g><g fill="#f1f5f0"><circle cx="32" cy="10" r="6"/><circle cx="32" cy="35" r="6"/><circle cx="32" cy="54" r="6"/><circle opacity=".25" cx="10" cy="35" r="5"/><circle opacity=".25" cx="54" cy="35" r="5"/></g></g><text x="108" y="62" fill="#f1f5f0" font-family="ui-monospace,Consolas,monospace" font-size="46" font-weight="600">jev-affected</text></svg>
|
package/assets/logo.svg
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="jev-affected semantic graph"><g fill="none" stroke="#161a17" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"><path opacity=".25" d="M32 10L10 35M32 10L54 35"/><path d="M32 10V54"/></g><g fill="#161a17"><circle cx="32" cy="10" r="6"/><circle cx="32" cy="35" r="6"/><circle cx="32" cy="54" r="6"/><circle opacity=".25" cx="10" cy="35" r="5"/><circle opacity=".25" cx="54" cy="35" r="5"/></g></svg>
|
|
Binary file
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { parseArgs } from "node:util";
|
|
6
|
+
import { TypeSafeClient } from "@typesafe-ai/sdk";
|
|
7
|
+
import { config as loadEnvFile } from "dotenv";
|
|
8
|
+
import { ConfigError, loadConfig, template } from "./config.js";
|
|
9
|
+
import { evaluate } from "./eval.js";
|
|
10
|
+
import { executePlan } from "./executor.js";
|
|
11
|
+
import { collectChanges, matches } from "./git.js";
|
|
12
|
+
import { createPlan } from "./planner.js";
|
|
13
|
+
loadEnvFile({ quiet: true });
|
|
14
|
+
function render(plan) {
|
|
15
|
+
return [
|
|
16
|
+
`jev-affected\n\nBase ${plan.base}\nHead ${plan.head}\nModel ${plan.model ?? "not analyzed"}`,
|
|
17
|
+
...plan.warnings.map((w) => `Warning: ${w}`),
|
|
18
|
+
"",
|
|
19
|
+
...plan.tasks.map((t) => `${t.decision.toUpperCase().padEnd(5)} ${t.id.padEnd(22)} ${t.probability?.toFixed(3) ?? "—"} ${t.reason}`),
|
|
20
|
+
"",
|
|
21
|
+
`${plan.tasks.filter((t) => t.decision === "run").length} / ${plan.tasks.length} tasks selected`,
|
|
22
|
+
].join("\n");
|
|
23
|
+
}
|
|
24
|
+
async function main() {
|
|
25
|
+
let args;
|
|
26
|
+
try {
|
|
27
|
+
args = parseArgs({
|
|
28
|
+
allowPositionals: true,
|
|
29
|
+
options: {
|
|
30
|
+
base: { type: "string" },
|
|
31
|
+
head: { type: "string" },
|
|
32
|
+
config: { type: "string" },
|
|
33
|
+
json: { type: "boolean" },
|
|
34
|
+
parallel: { type: "boolean" },
|
|
35
|
+
concurrency: { type: "string" },
|
|
36
|
+
help: { type: "boolean", short: "h" },
|
|
37
|
+
version: { type: "boolean", short: "v" },
|
|
38
|
+
"no-cache": { type: "boolean" },
|
|
39
|
+
fixtures: { type: "string" },
|
|
40
|
+
live: { type: "boolean" },
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
throw new ConfigError("Invalid arguments. See --help.");
|
|
46
|
+
}
|
|
47
|
+
const { values: v, positionals } = args;
|
|
48
|
+
const command = positionals[0];
|
|
49
|
+
if (v.version) {
|
|
50
|
+
console.log(JSON.parse(await readFile(new URL("../package.json", import.meta.url), "utf8")).version);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (v.help || !command) {
|
|
54
|
+
console.log(`jev-affected — Semantic task routing, powered by Jev.
|
|
55
|
+
|
|
56
|
+
Usage: jev-affected <command> [options]
|
|
57
|
+
Commands: init, plan, run, why <task>, inspect, doctor, eval
|
|
58
|
+
Options:
|
|
59
|
+
--base <ref> Compare from merge-base(ref, head)
|
|
60
|
+
--head <ref> Head commit (default HEAD; committed changes only)
|
|
61
|
+
--config <file> YAML configuration
|
|
62
|
+
--json Machine-readable output
|
|
63
|
+
--no-cache Bypass the local decision cache
|
|
64
|
+
--parallel Execute selected commands concurrently
|
|
65
|
+
--concurrency <n> Maximum parallel commands
|
|
66
|
+
--fixtures <dir> Evaluation fixtures (default evals/fixtures)
|
|
67
|
+
--live Evaluate using the real Jev API
|
|
68
|
+
--help, --version
|
|
69
|
+
|
|
70
|
+
inspect never sends requests. plan never executes task commands.`);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (!["init", "plan", "run", "why", "inspect", "doctor", "eval"].includes(command))
|
|
74
|
+
throw new ConfigError("Unknown command. See --help.");
|
|
75
|
+
if (positionals.length > (command === "why" ? 2 : 1))
|
|
76
|
+
throw new ConfigError("Unexpected positional arguments.");
|
|
77
|
+
const emit = (x) => console.log(JSON.stringify(x, null, 2));
|
|
78
|
+
if (command === "init") {
|
|
79
|
+
try {
|
|
80
|
+
await writeFile(String(v.config ?? "jev-affected.yml"), template, {
|
|
81
|
+
flag: "wx",
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
throw new ConfigError("Cannot create config: file already exists or directory is not writable.");
|
|
86
|
+
}
|
|
87
|
+
if (v.json)
|
|
88
|
+
emit({ created: v.config ?? "jev-affected.yml" });
|
|
89
|
+
else
|
|
90
|
+
console.log("Created jev-affected.yml\n\nNext:\n 1. Set TYPESAFE_API_KEY\n 2. Define semantic task conditions\n 3. Run: jev-affected plan");
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (command === "eval") {
|
|
94
|
+
const result = await evaluate(v.fixtures
|
|
95
|
+
? resolve(String(v.fixtures))
|
|
96
|
+
: fileURLToPath(new URL("../evals/fixtures", import.meta.url)), !!v.live);
|
|
97
|
+
emit(result);
|
|
98
|
+
if (!result.passed)
|
|
99
|
+
process.exitCode = 1;
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const config = await loadConfig(String(v.config ?? "jev-affected.yml"));
|
|
103
|
+
const opts = {
|
|
104
|
+
config,
|
|
105
|
+
base: typeof v.base === "string" ? v.base : undefined,
|
|
106
|
+
head: typeof v.head === "string" ? v.head : undefined,
|
|
107
|
+
cache: !v["no-cache"],
|
|
108
|
+
};
|
|
109
|
+
if (command === "why" &&
|
|
110
|
+
(!positionals[1] || !Object.hasOwn(config.tasks, positionals[1])))
|
|
111
|
+
throw new ConfigError("Specify a configured task: why <task>.");
|
|
112
|
+
if (command === "inspect") {
|
|
113
|
+
const state = await collectChanges(config, opts);
|
|
114
|
+
const questions = Object.fromEntries(Object.entries(config.tasks)
|
|
115
|
+
.filter(([, t]) => !state.incomplete &&
|
|
116
|
+
!t.always &&
|
|
117
|
+
t.allowSkip &&
|
|
118
|
+
state.files.some((f) => [f.path, ...(f.oldPath ? [f.oldPath] : [])].some((p) => (!t.include || matches(p, t.include)) &&
|
|
119
|
+
!matches(p, t.ignore))))
|
|
120
|
+
.map(([id, t]) => [id, { type: "noul", instructions: t.when }]));
|
|
121
|
+
emit({
|
|
122
|
+
state,
|
|
123
|
+
model: config.model,
|
|
124
|
+
questions,
|
|
125
|
+
providerRequest: Object.keys(questions).length
|
|
126
|
+
? {
|
|
127
|
+
model: config.model,
|
|
128
|
+
state: {
|
|
129
|
+
base: state.base,
|
|
130
|
+
head: state.head,
|
|
131
|
+
files: state.files,
|
|
132
|
+
diff: state.diff,
|
|
133
|
+
},
|
|
134
|
+
questions,
|
|
135
|
+
}
|
|
136
|
+
: null,
|
|
137
|
+
requestSent: false,
|
|
138
|
+
note: "No request was sent. Incomplete states use safe fallback.",
|
|
139
|
+
});
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (command === "doctor") {
|
|
143
|
+
const checks = {
|
|
144
|
+
node: Number(process.versions.node.split(".")[0]) >= 20,
|
|
145
|
+
config: true,
|
|
146
|
+
tasks: Object.keys(config.tasks).length.toString(),
|
|
147
|
+
apiKey: !!process.env.TYPESAFE_API_KEY?.trim(),
|
|
148
|
+
};
|
|
149
|
+
try {
|
|
150
|
+
await collectChanges(config, opts);
|
|
151
|
+
checks.gitBase = true;
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
checks.gitBase = false;
|
|
155
|
+
}
|
|
156
|
+
if (checks.apiKey) {
|
|
157
|
+
try {
|
|
158
|
+
await new TypeSafeClient({
|
|
159
|
+
logLevel: "off",
|
|
160
|
+
timeout: config.analysis.timeoutMs,
|
|
161
|
+
retry: { maxRetries: 0 },
|
|
162
|
+
}).models.list();
|
|
163
|
+
checks.apiReachable = true;
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
checks.apiReachable = false;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
else
|
|
170
|
+
checks.apiReachable = false;
|
|
171
|
+
emit(checks);
|
|
172
|
+
if (Object.values(checks).includes(false))
|
|
173
|
+
process.exitCode = 2;
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
const plan = await createPlan(opts);
|
|
177
|
+
if (command === "why") {
|
|
178
|
+
const task = plan.tasks.find((t) => t.id === positionals[1]);
|
|
179
|
+
emit({
|
|
180
|
+
task,
|
|
181
|
+
model: plan.model,
|
|
182
|
+
base: plan.base,
|
|
183
|
+
head: plan.head,
|
|
184
|
+
warnings: plan.warnings,
|
|
185
|
+
});
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
if (command === "run") {
|
|
189
|
+
const n = v.concurrency === undefined
|
|
190
|
+
? config.execution.concurrency
|
|
191
|
+
: Number(v.concurrency);
|
|
192
|
+
if (!Number.isInteger(n) || n < 1 || n > 64)
|
|
193
|
+
throw new ConfigError("concurrency must be an integer from 1 to 64");
|
|
194
|
+
if (!v.json)
|
|
195
|
+
console.log(render(plan));
|
|
196
|
+
process.exitCode = await executePlan(plan, {
|
|
197
|
+
concurrency: v.parallel || config.execution.parallel ? n : 1,
|
|
198
|
+
json: !!v.json,
|
|
199
|
+
});
|
|
200
|
+
if (v.json)
|
|
201
|
+
emit({ plan, exitCode: process.exitCode });
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
if (v.json)
|
|
205
|
+
emit(plan);
|
|
206
|
+
else
|
|
207
|
+
console.log(render(plan));
|
|
208
|
+
}
|
|
209
|
+
main().catch((error) => {
|
|
210
|
+
console.error(error instanceof ConfigError
|
|
211
|
+
? error.message
|
|
212
|
+
: "Operation failed. Check Git, paths and file permissions.");
|
|
213
|
+
process.exitCode = error instanceof ConfigError ? 2 : 3;
|
|
214
|
+
});
|