@effect-agent/pr-review 0.1.0-beta.11 → 0.1.0-beta.111
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/NOTICE +26 -0
- package/README.md +181 -172
- package/dist/Review.d.mts +294 -0
- package/dist/Review.mjs +738 -0
- package/dist/Review.mjs.map +1 -0
- package/dist/ReviewRepository-Wd_4qCaO.d.mts +71 -0
- package/dist/ReviewRepository.d.mts +2 -0
- package/dist/ReviewRepository.mjs +15 -0
- package/dist/ReviewRepository.mjs.map +1 -0
- package/dist/index.d.mts +3 -718
- package/dist/index.mjs +3 -66
- package/dist/repository-D7NN3225.mjs +101 -0
- package/dist/repository-D7NN3225.mjs.map +1 -0
- package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
- package/package.json +1 -54
- package/src/Review.ts +1111 -0
- package/src/ReviewRepository.ts +9 -0
- package/src/index.ts +2 -21
- package/src/internal/repository.ts +156 -0
- package/dist/action.d.mts +0 -190
- package/dist/action.mjs +0 -423
- package/dist/action.mjs.map +0 -1
- package/dist/cli.d.mts +0 -1
- package/dist/cli.mjs +0 -103
- package/dist/cli.mjs.map +0 -1
- package/dist/fan-out-TrA9EUCr.d.mts +0 -1085
- package/dist/github-5TCFrxfX.mjs +0 -1676
- package/dist/github-5TCFrxfX.mjs.map +0 -1
- package/dist/index.mjs.map +0 -1
- package/dist/providers-DobNWMUn.mjs +0 -990
- package/dist/providers-DobNWMUn.mjs.map +0 -1
- package/dist/testing.d.mts +0 -131
- package/dist/testing.mjs +0 -230
- package/dist/testing.mjs.map +0 -1
- package/src/action.ts +0 -697
- package/src/cli.ts +0 -214
- package/src/internal/action-entry.ts +0 -43
- package/src/internal/coverage.ts +0 -245
- package/src/internal/diff.ts +0 -134
- package/src/internal/effort.ts +0 -86
- package/src/internal/factory.ts +0 -374
- package/src/internal/fan-out-scripted.ts +0 -164
- package/src/internal/fan-out.ts +0 -450
- package/src/internal/fingerprint.ts +0 -74
- package/src/internal/fixtures.ts +0 -131
- package/src/internal/github-env.ts +0 -148
- package/src/internal/github.ts +0 -774
- package/src/internal/ignore.ts +0 -88
- package/src/internal/profiles.ts +0 -79
- package/src/internal/providers.ts +0 -94
- package/src/internal/render.ts +0 -428
- package/src/internal/retirement.ts +0 -332
- package/src/internal/review-agent.ts +0 -385
- package/src/internal/review-state.ts +0 -488
- package/src/internal/review-units.ts +0 -167
- package/src/internal/run.ts +0 -397
- package/src/internal/scripted.ts +0 -108
- package/src/internal/source.ts +0 -110
- package/src/testing.ts +0 -8
package/src/internal/ignore.ts
DELETED
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import { Effect, Layer } from "effect";
|
|
2
|
-
|
|
3
|
-
import { PullRequestMetadata, PullRequestSource, ReviewInputViolation } from "./source.ts";
|
|
4
|
-
|
|
5
|
-
// ---------------------------------------------------------------------------
|
|
6
|
-
// Configured ignore globs, applied at the source port. Ignored files are
|
|
7
|
-
// removed from the reviewer's entire observation surface — the changeset
|
|
8
|
-
// list, diffs, and head reads — so the model never spends budget on them and
|
|
9
|
-
// can never anchor a finding to them. Filtering fails closed: reading an
|
|
10
|
-
// ignored path is a ReviewInputViolation, exactly like a path outside the
|
|
11
|
-
// changeset.
|
|
12
|
-
// ---------------------------------------------------------------------------
|
|
13
|
-
|
|
14
|
-
const REGEX_SPECIALS = /[.+^${}()|[\]\\]/g;
|
|
15
|
-
|
|
16
|
-
// Placeholders for the directory-crossing wildcard while single-segment
|
|
17
|
-
// wildcards are rewritten; NUL/SOH cannot appear in a valid repository path.
|
|
18
|
-
const CROSSING_SLASH = "\u0000";
|
|
19
|
-
const CROSSING = "\u0001";
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* The supported glob vocabulary is deliberately minimal: `**` crosses
|
|
23
|
-
* directory separators, `*` and `?` stay within one path segment, everything
|
|
24
|
-
* else is literal. Every string compiles — there is no invalid pattern.
|
|
25
|
-
*/
|
|
26
|
-
const globToRegExpSource = (pattern: string): string =>
|
|
27
|
-
pattern
|
|
28
|
-
.replace(REGEX_SPECIALS, String.raw`\$&`)
|
|
29
|
-
.replaceAll("**/", CROSSING_SLASH)
|
|
30
|
-
.replaceAll("**", CROSSING)
|
|
31
|
-
.replaceAll("*", "[^/]*")
|
|
32
|
-
.replaceAll("?", "[^/]")
|
|
33
|
-
.replaceAll(CROSSING_SLASH, "(?:.*/)?")
|
|
34
|
-
.replaceAll(CROSSING, ".*");
|
|
35
|
-
|
|
36
|
-
/** Compile ignore globs into one predicate over repository-relative paths. */
|
|
37
|
-
export const compileIgnoreGlobs = (
|
|
38
|
-
patterns: ReadonlyArray<string>,
|
|
39
|
-
): ((path: string) => boolean) => {
|
|
40
|
-
if (patterns.length === 0) return () => false;
|
|
41
|
-
const expressions = patterns.map((pattern) => new RegExp(`^(?:${globToRegExpSource(pattern)})$`));
|
|
42
|
-
return (path) => expressions.some((expression) => expression.test(path));
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Decorate the ambient PullRequestSource with configured ignore globs. The
|
|
47
|
-
* resulting Layer requires the undecorated source, so callers provide their
|
|
48
|
-
* real adapter beneath it. Metadata's changed-file total is reduced by the
|
|
49
|
-
* ignored count: from the reviewer's perspective the ignored files do not
|
|
50
|
-
* exist, and truncation reporting stays about the reviewer's own bound.
|
|
51
|
-
*/
|
|
52
|
-
export const ignoringPullRequestSourceLayer = (
|
|
53
|
-
patterns: ReadonlyArray<string>,
|
|
54
|
-
): Layer.Layer<PullRequestSource, never, PullRequestSource> =>
|
|
55
|
-
Layer.effect(PullRequestSource)(
|
|
56
|
-
Effect.gen(function* () {
|
|
57
|
-
const source = yield* PullRequestSource;
|
|
58
|
-
const ignored = compileIgnoreGlobs(patterns);
|
|
59
|
-
const changedFiles = source.changedFiles.pipe(
|
|
60
|
-
Effect.map((files) => files.filter((file) => !ignored(file.path))),
|
|
61
|
-
);
|
|
62
|
-
const anchorFiles = source.anchorFiles.pipe(
|
|
63
|
-
Effect.map((files) => files.filter((file) => !ignored(file.path))),
|
|
64
|
-
);
|
|
65
|
-
const metadata = Effect.gen(function* () {
|
|
66
|
-
const [meta, files] = yield* Effect.all([source.metadata, source.anchorFiles]);
|
|
67
|
-
const ignoredCount = files.filter((file) => ignored(file.path)).length;
|
|
68
|
-
return PullRequestMetadata.make({
|
|
69
|
-
...meta,
|
|
70
|
-
totalChangedFiles: Math.max(0, meta.totalChangedFiles - ignoredCount),
|
|
71
|
-
});
|
|
72
|
-
});
|
|
73
|
-
return PullRequestSource.of({
|
|
74
|
-
metadata,
|
|
75
|
-
changedFiles,
|
|
76
|
-
anchorFiles,
|
|
77
|
-
readFile: (path) =>
|
|
78
|
-
ignored(path)
|
|
79
|
-
? Effect.fail(
|
|
80
|
-
ReviewInputViolation.make({
|
|
81
|
-
input: path,
|
|
82
|
-
reason: "Path is excluded from this review by configuration.",
|
|
83
|
-
}),
|
|
84
|
-
)
|
|
85
|
-
: source.readFile(path),
|
|
86
|
-
});
|
|
87
|
-
}),
|
|
88
|
-
);
|
package/src/internal/profiles.ts
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import { Schema } from "effect";
|
|
2
|
-
|
|
3
|
-
// ---------------------------------------------------------------------------
|
|
4
|
-
// Committed capability claims, schema-first: what this package's reviewers
|
|
5
|
-
// promise and — just as deliberately — what they never claim. Deployment
|
|
6
|
-
// class E (ephemeral): one bounded AgentRuntime.run per invocation, no
|
|
7
|
-
// durability, no exactly-once external effects (DUR-003).
|
|
8
|
-
// ---------------------------------------------------------------------------
|
|
9
|
-
|
|
10
|
-
/** The flat reviewer's committed capability claim. */
|
|
11
|
-
export class PullRequestReviewerProfile extends Schema.Class<PullRequestReviewerProfile>(
|
|
12
|
-
"@effect-agent/pr-review/PullRequestReviewerProfile",
|
|
13
|
-
)({
|
|
14
|
-
/** Ephemeral runtime: one bounded AgentRuntime.run per invocation. */
|
|
15
|
-
deploymentClass: Schema.Literal("E"),
|
|
16
|
-
/** Every model-callable tool is a read of the pull request; none mutate. */
|
|
17
|
-
readOnlyToolSurface: Schema.Literal(true),
|
|
18
|
-
/** The review is posted by the host AFTER the run settles, never by a tool. */
|
|
19
|
-
publicationOutsideAgentLoop: Schema.Literal(true),
|
|
20
|
-
/** Finding anchors are validated against the parsed diff before posting. */
|
|
21
|
-
anchorsValidatedBeforePublication: Schema.Literal(true),
|
|
22
|
-
/** The live profile is env-gated out of every ordinary test gate. */
|
|
23
|
-
liveProfileOptIn: Schema.Literal(true),
|
|
24
|
-
/** Never claimed at any phase (DUR-003). */
|
|
25
|
-
exactlyOnceExternalEffects: Schema.Literal(false),
|
|
26
|
-
}) {}
|
|
27
|
-
|
|
28
|
-
export const pullRequestReviewerProfile = PullRequestReviewerProfile.make({
|
|
29
|
-
deploymentClass: "E",
|
|
30
|
-
readOnlyToolSurface: true,
|
|
31
|
-
publicationOutsideAgentLoop: true,
|
|
32
|
-
anchorsValidatedBeforePublication: true,
|
|
33
|
-
liveProfileOptIn: true,
|
|
34
|
-
exactlyOnceExternalEffects: false,
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
/** The fan-out reviewer's committed capability claim, schema-first. */
|
|
38
|
-
export class FanOutReviewerProfile extends Schema.Class<FanOutReviewerProfile>(
|
|
39
|
-
"@effect-agent/pr-review/FanOutReviewerProfile",
|
|
40
|
-
)({
|
|
41
|
-
/** Ephemeral runtime: one bounded AgentRuntime.run per invocation. */
|
|
42
|
-
deploymentClass: Schema.Literal("E"),
|
|
43
|
-
/** Every model-callable tool — parent and child — is a read; none mutate. */
|
|
44
|
-
readOnlyToolSurface: Schema.Literal(true),
|
|
45
|
-
/** The review is posted by the host AFTER the run settles, never by a tool. */
|
|
46
|
-
publicationOutsideAgentLoop: Schema.Literal(true),
|
|
47
|
-
/** Child findings are untrusted; anchors are validated against the parsed diff. */
|
|
48
|
-
anchorsValidatedBeforePublication: Schema.Literal(true),
|
|
49
|
-
/** S1 attached ephemeral delegation at depth 1; nested delegation is rejected. */
|
|
50
|
-
attachedEphemeralDelegation: Schema.Literal(true),
|
|
51
|
-
/** A failed unit surfaces to the coordinator as a typed failed result, never retried. */
|
|
52
|
-
failedUnitsReportedNotRetried: Schema.Literal(true),
|
|
53
|
-
/** The live profile is env-gated out of every ordinary test gate. */
|
|
54
|
-
liveProfileOptIn: Schema.Literal(true),
|
|
55
|
-
/** Never claimed at any phase (DUR-003). */
|
|
56
|
-
exactlyOnceExternalEffects: Schema.Literal(false),
|
|
57
|
-
}) {}
|
|
58
|
-
|
|
59
|
-
export const fanOutReviewerProfile = FanOutReviewerProfile.make({
|
|
60
|
-
deploymentClass: "E",
|
|
61
|
-
readOnlyToolSurface: true,
|
|
62
|
-
publicationOutsideAgentLoop: true,
|
|
63
|
-
anchorsValidatedBeforePublication: true,
|
|
64
|
-
attachedEphemeralDelegation: true,
|
|
65
|
-
failedUnitsReportedNotRetried: true,
|
|
66
|
-
liveProfileOptIn: true,
|
|
67
|
-
exactlyOnceExternalEffects: false,
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
export const LIVE_GATE_ENV = "EFFECT_AGENT_LIVE";
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* `EFFECT_AGENT_LIVE=1` plus the named credential is the only enabling
|
|
74
|
-
* combination for live (network, billed) profiles.
|
|
75
|
-
*/
|
|
76
|
-
export const liveProfileEnabled = (
|
|
77
|
-
env: Record<string, string | undefined>,
|
|
78
|
-
credentialEnv: string,
|
|
79
|
-
): boolean => env[LIVE_GATE_ENV] === "1" && (env[credentialEnv] ?? "") !== "";
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
import { AnthropicClient, AnthropicLanguageModel } from "@effect/ai-anthropic";
|
|
2
|
-
import { OpenAiClient, OpenAiLanguageModel } from "@effect/ai-openai";
|
|
3
|
-
import { Config, Layer } from "effect";
|
|
4
|
-
import { FetchHttpClient } from "effect/unstable/http";
|
|
5
|
-
|
|
6
|
-
import { resolveEffortRung, type EffortAliasName, type EffortPosition } from "./effort.ts";
|
|
7
|
-
|
|
8
|
-
// ---------------------------------------------------------------------------
|
|
9
|
-
// Built-in provider bindings for the two host entrypoints (CLI and Action).
|
|
10
|
-
// The library itself stays provider-agnostic — the configuration factory
|
|
11
|
-
// takes any Effect AI Model — and these helpers exist so the batteries-
|
|
12
|
-
// included paths need one flag and one credential, nothing more. Client
|
|
13
|
-
// Layers carry their redacted credentials from configuration; the
|
|
14
|
-
// application supplies them at the edge (D-027).
|
|
15
|
-
// ---------------------------------------------------------------------------
|
|
16
|
-
|
|
17
|
-
export type ReviewProvider = "openai" | "anthropic";
|
|
18
|
-
|
|
19
|
-
export const DEFAULT_PROVIDER: ReviewProvider = "openai";
|
|
20
|
-
|
|
21
|
-
export const DEFAULT_MODEL: Record<ReviewProvider, string> = {
|
|
22
|
-
openai: "gpt-5.6-sol",
|
|
23
|
-
anthropic: "claude-sonnet-5",
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
export const PROVIDER_CREDENTIAL_ENV: Record<ReviewProvider, string> = {
|
|
27
|
-
openai: "OPENAI_API_KEY",
|
|
28
|
-
anthropic: "ANTHROPIC_API_KEY",
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Each provider's offered reasoning-effort ladder, cheapest first. The rungs
|
|
33
|
-
* that turn reasoning off (`none`, `minimal`) are deliberately not offered —
|
|
34
|
-
* no review run wants them. An `EffortPosition` resolves into the running
|
|
35
|
-
* provider's own ladder, so the same stored position survives a provider or
|
|
36
|
-
* model change.
|
|
37
|
-
*/
|
|
38
|
-
export const PROVIDER_EFFORT_RUNGS = {
|
|
39
|
-
openai: ["low", "medium", "high", "xhigh"],
|
|
40
|
-
anthropic: ["low", "medium", "high"],
|
|
41
|
-
} as const satisfies Record<
|
|
42
|
-
ReviewProvider,
|
|
43
|
-
readonly [EffortAliasName, ...ReadonlyArray<EffortAliasName>]
|
|
44
|
-
>;
|
|
45
|
-
|
|
46
|
-
/** One OpenAI review model binding with the package's structured-output settings. */
|
|
47
|
-
export const makeOpenAiReviewModel = (model?: string, effort?: EffortPosition) =>
|
|
48
|
-
OpenAiLanguageModel.model(model ?? DEFAULT_MODEL.openai, {
|
|
49
|
-
// OpenAI counts hidden reasoning tokens and visible answer tokens against
|
|
50
|
-
// this same ceiling. High-effort reviews can exhaust an 8k allowance
|
|
51
|
-
// after reading every file but before emitting their structured report.
|
|
52
|
-
max_output_tokens: 32_000,
|
|
53
|
-
store: false,
|
|
54
|
-
strictJsonSchema: true,
|
|
55
|
-
...(effort === undefined
|
|
56
|
-
? {}
|
|
57
|
-
: { reasoning: { effort: resolveEffortRung(effort, PROVIDER_EFFORT_RUNGS.openai) } }),
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
/** One Anthropic review model binding with the package's output settings. */
|
|
61
|
-
export const makeAnthropicReviewModel = (model?: string, effort?: EffortPosition) =>
|
|
62
|
-
AnthropicLanguageModel.model(model ?? DEFAULT_MODEL.anthropic, {
|
|
63
|
-
max_tokens: 8_000,
|
|
64
|
-
...(effort === undefined
|
|
65
|
-
? {}
|
|
66
|
-
: { output_config: { effort: resolveEffortRung(effort, PROVIDER_EFFORT_RUNGS.anthropic) } }),
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* The human-readable descriptor of one provider binding, e.g.
|
|
71
|
-
* `openai/gpt-5.6-sol (effort high)`. Rendered into the review footer and
|
|
72
|
-
* included in the changeset-fingerprint signature, so a provider, model, or
|
|
73
|
-
* effort change re-reviews instead of skipping.
|
|
74
|
-
*/
|
|
75
|
-
export const describeReviewModel = (
|
|
76
|
-
provider: ReviewProvider,
|
|
77
|
-
model?: string,
|
|
78
|
-
effort?: EffortPosition,
|
|
79
|
-
): string => {
|
|
80
|
-
const base = `${provider}/${model ?? DEFAULT_MODEL[provider]}`;
|
|
81
|
-
return effort === undefined
|
|
82
|
-
? base
|
|
83
|
-
: `${base} (effort ${resolveEffortRung(effort, PROVIDER_EFFORT_RUNGS[provider])})`;
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
/** The OpenAI client Layer, credential from `OPENAI_API_KEY`. */
|
|
87
|
-
export const openAiClientLayer = OpenAiClient.layerConfig({
|
|
88
|
-
apiKey: Config.redacted(PROVIDER_CREDENTIAL_ENV.openai),
|
|
89
|
-
}).pipe(Layer.provide(FetchHttpClient.layer));
|
|
90
|
-
|
|
91
|
-
/** The Anthropic client Layer, credential from `ANTHROPIC_API_KEY`. */
|
|
92
|
-
export const anthropicClientLayer = AnthropicClient.layerConfig({
|
|
93
|
-
apiKey: Config.redacted(PROVIDER_CREDENTIAL_ENV.anthropic),
|
|
94
|
-
}).pipe(Layer.provide(FetchHttpClient.layer));
|