jev-affected 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 +10 -0
- package/README.md +26 -7
- package/dist/cache.d.ts +8 -0
- package/dist/cache.js +35 -0
- package/dist/cli.js +38 -3
- package/dist/git.d.ts +6 -2
- package/dist/git.js +89 -13
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/planner.d.ts +2 -5
- package/dist/planner.js +3 -2
- package/package.json +1 -1
- package/skills/jev-affected/SKILL.md +11 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
- Add a repository-owned `jev-affected.yml` for dogfooding this project.
|
|
6
|
+
- Analyze committed, working-tree, or staged changes with explicit input modes.
|
|
7
|
+
- Detect pull-request base branches across GitHub Actions, GitLab CI, Buildkite,
|
|
8
|
+
and mapped CircleCI pipeline values.
|
|
9
|
+
- Inspect and safely clear the Git-local semantic decision cache.
|
|
10
|
+
- Avoid treating source text that mentions Git submodule markers as an actual
|
|
11
|
+
submodule diff.
|
|
12
|
+
|
|
3
13
|
## 0.2.0
|
|
4
14
|
|
|
5
15
|
- Read API credentials from the process environment only.
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
<p align="center"><strong>Semantic task routing for software development.</strong></p>
|
|
4
4
|
<p align="center">Run tasks based on what changed, not where it changed.</p>
|
|
5
|
-
<p align="center">v0.
|
|
5
|
+
<p align="center">v0.3.0 Public Beta · Node.js 20+ · TypeScript · MIT · Jev-powered</p>
|
|
6
6
|
|
|
7
7
|

|
|
8
8
|
|
|
@@ -50,7 +50,14 @@ npx jev-affected plan --base main
|
|
|
50
50
|
npx jev-affected run --base main
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
-
`plan` performs analysis but never executes task commands. `run` executes configured commands.
|
|
53
|
+
`plan` performs analysis but never executes task commands. `run` executes configured commands. The default mode compares committed changes. Use `--working-tree` to include branch commits, staged changes, unstaged changes and untracked files, or `--staged` to analyze only the Git index.
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
npx jev-affected plan --working-tree --base main
|
|
57
|
+
npx jev-affected plan --staged
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
`--working-tree` accepts `--base` but not `--head`. `--staged` compares the index with `HEAD` and cannot be combined with `--base` or `--head`.
|
|
54
61
|
|
|
55
62
|
## How it works
|
|
56
63
|
|
|
@@ -121,12 +128,13 @@ Low model probabilities are estimates, not a guarantee that a task is unnecessar
|
|
|
121
128
|
| `inspect` | Preview sanitized analysis inputs locally; no request |
|
|
122
129
|
| `doctor` | Check Node, config, Git base, API key and models endpoint |
|
|
123
130
|
| `eval [--live]` | Evaluate fixture decisions and false-skip rate |
|
|
131
|
+
| `cache [status\|clear]` | Inspect or remove the Git-local decision cache |
|
|
124
132
|
|
|
125
|
-
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.
|
|
133
|
+
Common flags: `--base`, `--head`, `--working-tree`, `--staged`, `--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.
|
|
126
134
|
|
|
127
135
|
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.
|
|
128
136
|
|
|
129
|
-
Base priority: CLI → config →
|
|
137
|
+
Base priority: CLI → config → detected CI target → `origin/HEAD` → `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.
|
|
130
138
|
|
|
131
139
|
## CI
|
|
132
140
|
|
|
@@ -141,9 +149,18 @@ Base priority: CLI → config → GitHub PR base branch → `origin/main` → `m
|
|
|
141
149
|
|
|
142
150
|
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.
|
|
143
151
|
|
|
152
|
+
Target branches are detected from GitHub Actions, GitLab CI and Buildkite variables. CircleCI exposes the PR base as a pipeline value rather than a legacy job environment variable; map it to `CIRCLE_PR_BASE_BRANCH` in the job when using automatic detection. `--base` and `base` in configuration remain the explicit overrides.
|
|
153
|
+
|
|
154
|
+
| CI | Base input |
|
|
155
|
+
| --- | --- |
|
|
156
|
+
| GitHub Actions | `GITHUB_BASE_REF` |
|
|
157
|
+
| GitLab CI | merge-request diff base SHA, target branch, then default branch |
|
|
158
|
+
| Buildkite | `BUILDKITE_PULL_REQUEST_BASE_BRANCH` |
|
|
159
|
+
| CircleCI | `CIRCLE_PR_BASE_BRANCH` supplied from the PR base pipeline value |
|
|
160
|
+
|
|
144
161
|
## Agents
|
|
145
162
|
|
|
146
|
-
|
|
163
|
+
Run `jev-affected plan --json --working-tree` while editing, or use the default committed mode after committing. Execute all tasks whose `decision` is `run`, or use `jev-affected run` with the same mode. `inspect` lets you review what could leave the repository first.
|
|
147
164
|
|
|
148
165
|
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.
|
|
149
166
|
|
|
@@ -159,6 +176,8 @@ const plan = await createPlan({ config, base: 'main' });
|
|
|
159
176
|
|
|
160
177
|
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.
|
|
161
178
|
|
|
179
|
+
Use `jev-affected cache status` to inspect entry count and size, and `jev-affected cache clear` to remove only the cache directory resolved inside Git metadata. Both commands support `--json` and do not require a project configuration file.
|
|
180
|
+
|
|
162
181
|
## Evaluation
|
|
163
182
|
|
|
164
183
|
```sh
|
|
@@ -184,11 +203,11 @@ No backend and no telemetry. Data is read locally; semantic inputs go directly t
|
|
|
184
203
|
|
|
185
204
|
**What if Jev is down?** Candidate tasks run. Deterministic rules and protected tasks still apply.
|
|
186
205
|
|
|
187
|
-
**Are uncommitted files included?**
|
|
206
|
+
**Are uncommitted files included?** Only with `--working-tree` or `--staged`. Default mode compares commits so explicit refs remain reproducible.
|
|
188
207
|
|
|
189
208
|
## Roadmap
|
|
190
209
|
|
|
191
|
-
|
|
210
|
+
The current release focuses on inspectable semantic dependencies and safe plans for committed, working-tree and staged changes. Later candidates include historical evaluations, watch mode, task groups and Nx/Turbo adapters. No GUI, SaaS, MCP server or autonomous command generation is included.
|
|
192
211
|
|
|
193
212
|
## Contributing and license
|
|
194
213
|
|
package/dist/cache.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface CacheInfo {
|
|
2
|
+
path: string;
|
|
3
|
+
entries: number;
|
|
4
|
+
bytes: number;
|
|
5
|
+
}
|
|
6
|
+
export declare function cacheDirectory(cwd?: string): Promise<string>;
|
|
7
|
+
export declare function inspectCache(cwd?: string): Promise<CacheInfo>;
|
|
8
|
+
export declare function clearCache(cwd?: string): Promise<CacheInfo>;
|
package/dist/cache.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { readdir, rm, stat } from "node:fs/promises";
|
|
2
|
+
import { isAbsolute, relative, resolve } from "node:path";
|
|
3
|
+
import { ConfigError } from "./config.js";
|
|
4
|
+
import { git } from "./git.js";
|
|
5
|
+
export async function cacheDirectory(cwd = process.cwd()) {
|
|
6
|
+
const common = resolve(cwd, (await git(cwd, "rev-parse", "--git-common-dir")).trim());
|
|
7
|
+
const directory = resolve(cwd, (await git(cwd, "rev-parse", "--git-path", "jev-affected/cache")).trim());
|
|
8
|
+
const fromCommon = relative(common, directory);
|
|
9
|
+
if (!fromCommon || fromCommon.startsWith("..") || isAbsolute(fromCommon))
|
|
10
|
+
throw new ConfigError("Cannot resolve a safe Git cache directory.");
|
|
11
|
+
return directory;
|
|
12
|
+
}
|
|
13
|
+
export async function inspectCache(cwd = process.cwd()) {
|
|
14
|
+
const path = await cacheDirectory(cwd);
|
|
15
|
+
let names;
|
|
16
|
+
try {
|
|
17
|
+
names = (await readdir(path)).filter((name) => name.endsWith(".json"));
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
if (error.code === "ENOENT")
|
|
21
|
+
return { path, entries: 0, bytes: 0 };
|
|
22
|
+
throw error;
|
|
23
|
+
}
|
|
24
|
+
const sizes = await Promise.all(names.map(async (name) => (await stat(resolve(path, name))).size));
|
|
25
|
+
return {
|
|
26
|
+
path,
|
|
27
|
+
entries: names.length,
|
|
28
|
+
bytes: sizes.reduce((total, size) => total + size, 0),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export async function clearCache(cwd = process.cwd()) {
|
|
32
|
+
const info = await inspectCache(cwd);
|
|
33
|
+
await rm(info.path, { recursive: true, force: true });
|
|
34
|
+
return info;
|
|
35
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -4,6 +4,7 @@ import { resolve } from "node:path";
|
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { parseArgs } from "node:util";
|
|
6
6
|
import { TypeSafeClient } from "@typesafe-ai/sdk";
|
|
7
|
+
import { clearCache, inspectCache } from "./cache.js";
|
|
7
8
|
import { ConfigError, loadConfig, resolveApiKey, template } from "./config.js";
|
|
8
9
|
import { evaluate } from "./eval.js";
|
|
9
10
|
import { executePlan } from "./executor.js";
|
|
@@ -34,6 +35,8 @@ async function main() {
|
|
|
34
35
|
help: { type: "boolean", short: "h" },
|
|
35
36
|
version: { type: "boolean", short: "v" },
|
|
36
37
|
"no-cache": { type: "boolean" },
|
|
38
|
+
"working-tree": { type: "boolean" },
|
|
39
|
+
staged: { type: "boolean" },
|
|
37
40
|
fixtures: { type: "string" },
|
|
38
41
|
live: { type: "boolean" },
|
|
39
42
|
},
|
|
@@ -52,13 +55,15 @@ async function main() {
|
|
|
52
55
|
console.log(`jev-affected — Semantic task routing, powered by Jev.
|
|
53
56
|
|
|
54
57
|
Usage: jev-affected <command> [options]
|
|
55
|
-
Commands: init, plan, run, why <task>, inspect, doctor, eval
|
|
58
|
+
Commands: init, plan, run, why <task>, inspect, doctor, eval, cache [status|clear]
|
|
56
59
|
Options:
|
|
57
60
|
--base <ref> Compare from merge-base(ref, head)
|
|
58
61
|
--head <ref> Head commit (default HEAD; committed changes only)
|
|
59
62
|
--config <file> YAML configuration
|
|
60
63
|
--json Machine-readable output
|
|
61
64
|
--no-cache Bypass the local decision cache
|
|
65
|
+
--working-tree Include committed, staged, unstaged and untracked changes
|
|
66
|
+
--staged Analyze only changes staged against HEAD
|
|
62
67
|
--parallel Execute selected commands concurrently
|
|
63
68
|
--concurrency <n> Maximum parallel commands
|
|
64
69
|
--fixtures <dir> Evaluation fixtures (default evals/fixtures)
|
|
@@ -68,11 +73,39 @@ Options:
|
|
|
68
73
|
inspect never sends requests. plan never executes task commands.`);
|
|
69
74
|
return;
|
|
70
75
|
}
|
|
71
|
-
if (![
|
|
76
|
+
if (![
|
|
77
|
+
"init",
|
|
78
|
+
"plan",
|
|
79
|
+
"run",
|
|
80
|
+
"why",
|
|
81
|
+
"inspect",
|
|
82
|
+
"doctor",
|
|
83
|
+
"eval",
|
|
84
|
+
"cache",
|
|
85
|
+
].includes(command))
|
|
72
86
|
throw new ConfigError("Unknown command. See --help.");
|
|
73
|
-
if (positionals.length > (
|
|
87
|
+
if (positionals.length > (["why", "cache"].includes(command) ? 2 : 1))
|
|
74
88
|
throw new ConfigError("Unexpected positional arguments.");
|
|
89
|
+
if (v["working-tree"] && v.staged)
|
|
90
|
+
throw new ConfigError("Use --working-tree or --staged, not both.");
|
|
91
|
+
if (v["working-tree"] && v.head)
|
|
92
|
+
throw new ConfigError("--working-tree cannot be combined with --head.");
|
|
93
|
+
if (v.staged && (v.base || v.head))
|
|
94
|
+
throw new ConfigError("--staged cannot be combined with --base or --head.");
|
|
75
95
|
const emit = (x) => console.log(JSON.stringify(x, null, 2));
|
|
96
|
+
if (command === "cache") {
|
|
97
|
+
const action = positionals[1] ?? "status";
|
|
98
|
+
if (!["status", "clear"].includes(action))
|
|
99
|
+
throw new ConfigError("Use cache status or cache clear.");
|
|
100
|
+
const info = action === "clear" ? await clearCache() : await inspectCache();
|
|
101
|
+
if (v.json)
|
|
102
|
+
emit({ action, ...info });
|
|
103
|
+
else if (action === "clear")
|
|
104
|
+
console.log(`Cleared ${info.entries} cache entries (${info.bytes} bytes).`);
|
|
105
|
+
else
|
|
106
|
+
console.log(`Cache: ${info.entries} entries, ${info.bytes} bytes\n${info.path}`);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
76
109
|
if (command === "init") {
|
|
77
110
|
try {
|
|
78
111
|
await writeFile(String(v.config ?? "jev-affected.yml"), template, {
|
|
@@ -103,6 +136,8 @@ inspect never sends requests. plan never executes task commands.`);
|
|
|
103
136
|
base: typeof v.base === "string" ? v.base : undefined,
|
|
104
137
|
head: typeof v.head === "string" ? v.head : undefined,
|
|
105
138
|
cache: !v["no-cache"],
|
|
139
|
+
workingTree: !!v["working-tree"],
|
|
140
|
+
staged: !!v.staged,
|
|
106
141
|
};
|
|
107
142
|
if (command === "why" &&
|
|
108
143
|
(!positionals[1] || !Object.hasOwn(config.tasks, positionals[1])))
|
package/dist/git.d.ts
CHANGED
|
@@ -14,8 +14,12 @@ export interface ChangeState {
|
|
|
14
14
|
incomplete: boolean;
|
|
15
15
|
warnings: string[];
|
|
16
16
|
}
|
|
17
|
-
export
|
|
17
|
+
export interface ChangeOptions {
|
|
18
18
|
cwd?: string;
|
|
19
19
|
base?: string;
|
|
20
20
|
head?: string;
|
|
21
|
-
|
|
21
|
+
workingTree?: boolean;
|
|
22
|
+
staged?: boolean;
|
|
23
|
+
}
|
|
24
|
+
export declare function detectCiBaseCandidates(env?: NodeJS.ProcessEnv): string[];
|
|
25
|
+
export declare function collectChanges(config: Config, options?: ChangeOptions): Promise<ChangeState>;
|
package/dist/git.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { execFile } from "node:child_process";
|
|
2
|
+
import { stat } from "node:fs/promises";
|
|
3
|
+
import { resolve as resolvePath } from "node:path";
|
|
2
4
|
import { promisify } from "node:util";
|
|
3
5
|
import picomatch from "picomatch";
|
|
4
6
|
import { ConfigError, secretPatterns } from "./config.js";
|
|
@@ -12,38 +14,88 @@ export async function git(cwd, ...args) {
|
|
|
12
14
|
})).stdout;
|
|
13
15
|
}
|
|
14
16
|
export const matches = (path, patterns) => patterns.length > 0 && picomatch(patterns, { dot: true })(path);
|
|
17
|
+
const branchRefs = (value) => {
|
|
18
|
+
const branch = value?.trim().replace(/^refs\/heads\//, "");
|
|
19
|
+
if (!branch || branch === "false")
|
|
20
|
+
return [];
|
|
21
|
+
if (branch.startsWith("origin/") || /^[0-9a-f]{7,40}$/.test(branch))
|
|
22
|
+
return [branch];
|
|
23
|
+
return [`origin/${branch}`, branch];
|
|
24
|
+
};
|
|
25
|
+
export function detectCiBaseCandidates(env = process.env) {
|
|
26
|
+
const refs = [
|
|
27
|
+
...branchRefs(env.GITHUB_BASE_REF),
|
|
28
|
+
...(env.GITLAB_CI === "true"
|
|
29
|
+
? [
|
|
30
|
+
...branchRefs(env.CI_MERGE_REQUEST_DIFF_BASE_SHA),
|
|
31
|
+
...branchRefs(env.CI_MERGE_REQUEST_TARGET_BRANCH_NAME),
|
|
32
|
+
...branchRefs(env.CI_DEFAULT_BRANCH),
|
|
33
|
+
]
|
|
34
|
+
: []),
|
|
35
|
+
...(env.BUILDKITE === "true"
|
|
36
|
+
? branchRefs(env.BUILDKITE_PULL_REQUEST_BASE_BRANCH)
|
|
37
|
+
: []),
|
|
38
|
+
...(env.CIRCLECI === "true" ? branchRefs(env.CIRCLE_PR_BASE_BRANCH) : []),
|
|
39
|
+
];
|
|
40
|
+
return [...new Set(refs)];
|
|
41
|
+
}
|
|
42
|
+
const hasUnsupportedPatch = (patch) => /^Binary files .+ differ$/m.test(patch) ||
|
|
43
|
+
/^[+-]Subproject commit [0-9a-f]{7,}(?:-dirty)?$/m.test(patch);
|
|
44
|
+
async function untrackedPatch(cwd, path) {
|
|
45
|
+
try {
|
|
46
|
+
return await git(cwd, "diff", "--no-index", "--no-ext-diff", "--no-textconv", "--unified=3", "--", "/dev/null", path);
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
const result = error;
|
|
50
|
+
if (Number(result.code) === 1 && typeof result.stdout === "string")
|
|
51
|
+
return result.stdout;
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
15
55
|
export async function collectChanges(config, options = {}) {
|
|
16
56
|
const cwd = options.cwd ?? process.cwd();
|
|
57
|
+
if (options.workingTree && options.staged)
|
|
58
|
+
throw new ConfigError("Use --working-tree or --staged, not both.");
|
|
59
|
+
if (options.workingTree && options.head)
|
|
60
|
+
throw new ConfigError("--working-tree cannot be combined with --head.");
|
|
61
|
+
if (options.staged && (options.base || options.head))
|
|
62
|
+
throw new ConfigError("--staged cannot be combined with --base or --head.");
|
|
17
63
|
const resolve = async (ref) => (await git(cwd, "rev-parse", "--verify", "--end-of-options", `${ref}^{commit}`)).trim();
|
|
18
|
-
let
|
|
64
|
+
let headCommit;
|
|
19
65
|
try {
|
|
20
|
-
|
|
66
|
+
headCommit = await resolve(options.head ?? "HEAD");
|
|
21
67
|
}
|
|
22
68
|
catch {
|
|
23
69
|
throw new ConfigError("Cannot resolve head commit.");
|
|
24
70
|
}
|
|
25
|
-
const explicit = options.base ?? config.base;
|
|
71
|
+
const explicit = options.staged ? "HEAD" : (options.base ?? config.base);
|
|
26
72
|
const candidates = explicit
|
|
27
73
|
? [explicit]
|
|
28
74
|
: [
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
: undefined,
|
|
75
|
+
...detectCiBaseCandidates(),
|
|
76
|
+
"origin/HEAD",
|
|
32
77
|
"origin/main",
|
|
33
78
|
"main",
|
|
34
79
|
"master",
|
|
35
|
-
]
|
|
80
|
+
];
|
|
36
81
|
let base;
|
|
37
82
|
for (const ref of candidates) {
|
|
38
83
|
try {
|
|
39
|
-
base =
|
|
84
|
+
base = options.staged
|
|
85
|
+
? await resolve("HEAD")
|
|
86
|
+
: (await git(cwd, "merge-base", await resolve(ref), headCommit)).trim();
|
|
40
87
|
break;
|
|
41
88
|
}
|
|
42
89
|
catch { }
|
|
43
90
|
}
|
|
44
91
|
if (!base)
|
|
45
92
|
throw new ConfigError("Cannot resolve Git base. Fetch the base branch or pass --base.");
|
|
46
|
-
const
|
|
93
|
+
const comparison = options.staged
|
|
94
|
+
? ["--cached", base]
|
|
95
|
+
: options.workingTree
|
|
96
|
+
? [base]
|
|
97
|
+
: [base, headCommit];
|
|
98
|
+
const parts = (await git(cwd, "diff", "--name-status", "-z", "--find-renames", ...comparison, "--")).split("\0");
|
|
47
99
|
const files = [];
|
|
48
100
|
for (let i = 0; i < parts.length && parts[i];) {
|
|
49
101
|
const status = parts[i++];
|
|
@@ -59,9 +111,23 @@ export async function collectChanges(config, options = {}) {
|
|
|
59
111
|
else
|
|
60
112
|
files.push({ status, path });
|
|
61
113
|
}
|
|
114
|
+
if (options.workingTree) {
|
|
115
|
+
const tracked = new Set(files.flatMap((file) => [
|
|
116
|
+
file.path,
|
|
117
|
+
...(file.oldPath ? [file.oldPath] : []),
|
|
118
|
+
]));
|
|
119
|
+
for (const path of (await git(cwd, "ls-files", "--others", "--exclude-standard", "-z", "--")).split("\0")) {
|
|
120
|
+
if (path && !tracked.has(path))
|
|
121
|
+
files.push({ status: "A", path });
|
|
122
|
+
}
|
|
123
|
+
}
|
|
62
124
|
const state = {
|
|
63
125
|
base,
|
|
64
|
-
head
|
|
126
|
+
head: options.staged
|
|
127
|
+
? "INDEX"
|
|
128
|
+
: options.workingTree
|
|
129
|
+
? "WORKTREE"
|
|
130
|
+
: headCommit,
|
|
65
131
|
files: [],
|
|
66
132
|
diff: "",
|
|
67
133
|
incomplete: false,
|
|
@@ -78,9 +144,19 @@ export async function collectChanges(config, options = {}) {
|
|
|
78
144
|
continue;
|
|
79
145
|
state.files.push(file);
|
|
80
146
|
try {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
147
|
+
if (options.workingTree && file.status === "A") {
|
|
148
|
+
const size = (await stat(resolvePath(cwd, file.path))).size;
|
|
149
|
+
if (Buffer.byteLength(state.diff) + size >
|
|
150
|
+
config.analysis.maxDiffBytes) {
|
|
151
|
+
state.incomplete = true;
|
|
152
|
+
state.warnings.push("Diff exceeds analysis limit; running all tasks.");
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
const patch = options.workingTree && file.status === "A"
|
|
157
|
+
? await untrackedPatch(cwd, file.path)
|
|
158
|
+
: await git(cwd, "diff", "--no-ext-diff", "--no-textconv", "--find-renames", "--unified=3", ...(options.staged ? ["--cached", base] : comparison), "--", ...paths);
|
|
159
|
+
if (hasUnsupportedPatch(patch)) {
|
|
84
160
|
state.incomplete = true;
|
|
85
161
|
state.warnings.push("Binary or submodule change; running all tasks.");
|
|
86
162
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
export { type CacheInfo, cacheDirectory, clearCache, inspectCache, } from "./cache.js";
|
|
1
2
|
export { API_KEY_VARS, type Config, ConfigError, loadConfig, parseConfig, resolveApiKey, } from "./config.js";
|
|
2
3
|
export { executePlan } from "./executor.js";
|
|
3
|
-
export { type ChangeState, collectChanges } from "./git.js";
|
|
4
|
+
export { type ChangedFile, type ChangeOptions, type ChangeState, collectChanges, detectCiBaseCandidates, } from "./git.js";
|
|
4
5
|
export { createPlan, type Plan, type TaskDecision } from "./planner.js";
|
|
5
6
|
export { type AnalysisInput, type AnalysisResult, type DecisionProvider, JevProvider, } from "./provider.js";
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
export { cacheDirectory, clearCache, inspectCache, } from "./cache.js";
|
|
1
2
|
export { API_KEY_VARS, ConfigError, loadConfig, parseConfig, resolveApiKey, } from "./config.js";
|
|
2
3
|
export { executePlan } from "./executor.js";
|
|
3
|
-
export { collectChanges } from "./git.js";
|
|
4
|
+
export { collectChanges, detectCiBaseCandidates, } from "./git.js";
|
|
4
5
|
export { createPlan } from "./planner.js";
|
|
5
6
|
export { JevProvider, } from "./provider.js";
|
package/dist/planner.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Config } from "./config.js";
|
|
2
|
-
import { type ChangeState } from "./git.js";
|
|
2
|
+
import { type ChangeOptions, type ChangeState } from "./git.js";
|
|
3
3
|
import { type AnalysisResult, type DecisionProvider } from "./provider.js";
|
|
4
4
|
export interface TaskDecision {
|
|
5
5
|
id: string;
|
|
@@ -28,10 +28,7 @@ export interface Plan {
|
|
|
28
28
|
}
|
|
29
29
|
export declare function createPlan(input: {
|
|
30
30
|
config: Config;
|
|
31
|
-
cwd?: string;
|
|
32
|
-
base?: string;
|
|
33
|
-
head?: string;
|
|
34
31
|
state?: ChangeState;
|
|
35
32
|
provider?: DecisionProvider;
|
|
36
33
|
cache?: boolean;
|
|
37
|
-
}): Promise<Plan>;
|
|
34
|
+
} & ChangeOptions): Promise<Plan>;
|
package/dist/planner.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
3
|
import { resolve } from "node:path";
|
|
4
|
+
import { cacheDirectory } from "./cache.js";
|
|
4
5
|
import { parseConfig, resolveApiKey, secretPatterns, } from "./config.js";
|
|
5
|
-
import { collectChanges,
|
|
6
|
+
import { collectChanges, matches, } from "./git.js";
|
|
6
7
|
import { JevProvider, } from "./provider.js";
|
|
7
8
|
export async function createPlan(input) {
|
|
8
9
|
const start = Date.now(), config = parseConfig(input.config), cwd = input.cwd ?? process.cwd();
|
|
@@ -60,7 +61,7 @@ export async function createPlan(input) {
|
|
|
60
61
|
const pinned = /^jev-\d+\.\d+\.\d+$/.test(config.model);
|
|
61
62
|
if (input.cache !== false && !input.provider && pinned) {
|
|
62
63
|
try {
|
|
63
|
-
const dir =
|
|
64
|
+
const dir = await cacheDirectory(cwd);
|
|
64
65
|
const key = createHash("sha256")
|
|
65
66
|
.update(JSON.stringify({
|
|
66
67
|
version: 1,
|
package/package.json
CHANGED
|
@@ -13,12 +13,13 @@ projects and `npx jev-affected` elsewhere.
|
|
|
13
13
|
1. Confirm the current directory is a Git repository and locate
|
|
14
14
|
`jev-affected.yml`. Run `jev-affected init` only when setup is requested and no
|
|
15
15
|
configuration exists.
|
|
16
|
-
2.
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
2. Choose the input mode. Use the default mode for committed branch changes,
|
|
17
|
+
`--working-tree --base <ref>` while editing, or `--staged` for the exact Git
|
|
18
|
+
index before a commit.
|
|
19
|
+
3. On first use, or when privacy matters, run `jev-affected inspect` with the
|
|
20
|
+
same mode and base flags intended for planning. This previews the sanitized
|
|
21
|
+
input locally without sending a request.
|
|
22
|
+
4. Run `jev-affected plan --json` with those same flags and use its task decisions as the
|
|
22
23
|
record of what should run. Do not invent task reasons or commands.
|
|
23
24
|
5. Run `jev-affected run --base <ref>` only when execution is requested. Commands
|
|
24
25
|
come from the repository configuration and should be treated as repository
|
|
@@ -26,6 +27,10 @@ projects and `npx jev-affected` elsewhere.
|
|
|
26
27
|
6. Report the selected and skipped task counts, actual model version, warnings,
|
|
27
28
|
fallback decisions, and any failed task commands.
|
|
28
29
|
|
|
30
|
+
Use `jev-affected cache status` to inspect the Git-local decision cache. Use
|
|
31
|
+
`jev-affected cache clear` when a fresh semantic decision is needed; it removes
|
|
32
|
+
only the cache directory resolved inside Git metadata.
|
|
33
|
+
|
|
29
34
|
Use an explicit base ref in CI and shallow clones. Fetch enough history for Git to
|
|
30
35
|
compute the merge base.
|
|
31
36
|
|