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.
Files changed (47) hide show
  1. package/.env.example +3 -0
  2. package/CHANGELOG.md +8 -0
  3. package/CODE_OF_CONDUCT.md +7 -0
  4. package/CONTRIBUTING.md +16 -0
  5. package/EVALUATION.md +77 -0
  6. package/LICENSE +21 -0
  7. package/NOTICE +6 -0
  8. package/README.md +201 -0
  9. package/SECURITY.md +21 -0
  10. package/assets/README.md +5 -0
  11. package/assets/demo.gif +0 -0
  12. package/assets/demo.mp4 +0 -0
  13. package/assets/favicon.svg +1 -0
  14. package/assets/logo-dark.svg +1 -0
  15. package/assets/logo-mark.svg +1 -0
  16. package/assets/logo-wordmark.svg +1 -0
  17. package/assets/logo.svg +1 -0
  18. package/assets/social-card.png +0 -0
  19. package/dist/cli.d.ts +2 -0
  20. package/dist/cli.js +214 -0
  21. package/dist/config.d.ts +41 -0
  22. package/dist/config.js +97 -0
  23. package/dist/eval.d.ts +41 -0
  24. package/dist/eval.js +96 -0
  25. package/dist/executor.d.ts +6 -0
  26. package/dist/executor.js +68 -0
  27. package/dist/git.d.ts +21 -0
  28. package/dist/git.js +102 -0
  29. package/dist/index.d.ts +5 -0
  30. package/dist/index.js +5 -0
  31. package/dist/planner.d.ts +37 -0
  32. package/dist/planner.js +132 -0
  33. package/dist/provider.d.ts +21 -0
  34. package/dist/provider.js +28 -0
  35. package/evals/fixtures/api-response-field.json +53 -0
  36. package/evals/fixtures/auth-session-ttl.json +53 -0
  37. package/evals/fixtures/comment-only.json +53 -0
  38. package/evals/fixtures/db-schema.json +53 -0
  39. package/evals/fixtures/docs-only.json +53 -0
  40. package/evals/fixtures/logging-only.json +53 -0
  41. package/evals/fixtures/performance.json +53 -0
  42. package/examples/basic/jev-affected.yml +16 -0
  43. package/package.json +84 -0
  44. package/skills/jev-affected/SKILL.md +51 -0
  45. package/skills/jev-affected/agents/openai.yaml +7 -0
  46. package/skills/jev-affected/assets/icon-large.svg +4 -0
  47. package/skills/jev-affected/assets/icon-small.svg +4 -0
package/.env.example ADDED
@@ -0,0 +1,3 @@
1
+ # Copy this file to .env and replace the placeholder.
2
+ # .env is ignored by Git. Never commit a real API key.
3
+ TYPESAFE_API_KEY=replace_with_your_typesafe_api_key
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.
@@ -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
@@ -0,0 +1,6 @@
1
+ jev-affected
2
+ Copyright 2026 jammaru
3
+
4
+ Independent open-source software. Not affiliated with or endorsed by TypeSafe AI.
5
+ jev-affected is distributed under the MIT License.
6
+ The TypeSafe JavaScript SDK is distributed separately under its MIT license.
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
+ ![Illustrative offline fixture demo, not live Jev output](assets/demo.gif)
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.
@@ -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`.
Binary file
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>
@@ -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
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
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
+ });