@effect-agent/pr-review 0.1.0-beta.27 → 0.1.0-beta.29
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/README.md +9 -204
- package/dist/index.d.mts +87 -914
- package/dist/index.mjs +163 -71
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -18
- package/src/index.ts +1 -25
- package/src/review.ts +212 -0
- package/dist/action.d.mts +0 -215
- package/dist/action.mjs +0 -505
- package/dist/action.mjs.map +0 -1
- package/dist/cli.d.mts +0 -1
- package/dist/cli.mjs +0 -106
- package/dist/cli.mjs.map +0 -1
- package/dist/fan-out-C3yG1cx3.d.mts +0 -1526
- package/dist/github-CCuLgyqb.mjs +0 -3437
- package/dist/github-CCuLgyqb.mjs.map +0 -1
- package/dist/logging-Q4j0oub-.mjs +0 -75
- package/dist/logging-Q4j0oub-.mjs.map +0 -1
- package/dist/providers-Br9FRn7j.mjs +0 -1349
- package/dist/providers-Br9FRn7j.mjs.map +0 -1
- package/dist/testing.d.mts +0 -86
- package/dist/testing.mjs +0 -184
- package/dist/testing.mjs.map +0 -1
- package/src/action.ts +0 -906
- package/src/cli.ts +0 -235
- package/src/internal/action-entry.ts +0 -45
- package/src/internal/adjudication.ts +0 -415
- package/src/internal/anchors.ts +0 -20
- package/src/internal/coverage.ts +0 -357
- package/src/internal/diff.ts +0 -193
- package/src/internal/effort.ts +0 -86
- package/src/internal/factory.ts +0 -357
- package/src/internal/fan-out-scripted.ts +0 -77
- package/src/internal/fan-out.ts +0 -1148
- package/src/internal/fingerprint.ts +0 -89
- package/src/internal/fixtures.ts +0 -148
- package/src/internal/github-env.ts +0 -164
- package/src/internal/github.ts +0 -1218
- package/src/internal/ignore.ts +0 -88
- package/src/internal/logging.ts +0 -124
- package/src/internal/profiles.ts +0 -91
- package/src/internal/progress.ts +0 -433
- package/src/internal/providers.ts +0 -133
- package/src/internal/render.ts +0 -819
- package/src/internal/retirement.ts +0 -337
- package/src/internal/review-agent.ts +0 -543
- package/src/internal/review-state.ts +0 -782
- package/src/internal/review-units.ts +0 -493
- package/src/internal/run.ts +0 -611
- 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/logging.ts
DELETED
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
import type { LogLevel } from "effect";
|
|
2
|
-
import { Cause, Config, Effect, Layer, Logger, Predicate, References } from "effect";
|
|
3
|
-
|
|
4
|
-
// ---------------------------------------------------------------------------
|
|
5
|
-
// Compact host logging for CI runs. The engine's telemetry logs carry full
|
|
6
|
-
// OTel-style annotation sets — correct for an exporter, unreadable as an
|
|
7
|
-
// Actions console. This logger renders each record as ONE line: known engine
|
|
8
|
-
// telemetry messages become short progress lines, and everything else keeps
|
|
9
|
-
// its message with annotations compacted (warnings and errors only). It is
|
|
10
|
-
// presentation only — record filtering stays with MinimumLogLevel, and
|
|
11
|
-
// nothing here feeds back into the run.
|
|
12
|
-
// ---------------------------------------------------------------------------
|
|
13
|
-
|
|
14
|
-
/** Everything one compact line is rendered from; pure and directly testable. */
|
|
15
|
-
export interface CompactLogRecord {
|
|
16
|
-
readonly date: Date;
|
|
17
|
-
readonly logLevel: LogLevel.LogLevel;
|
|
18
|
-
readonly message: unknown;
|
|
19
|
-
readonly annotations: Readonly<Record<string, unknown>>;
|
|
20
|
-
/** Pretty-rendered Cause, present only when the record carries one. */
|
|
21
|
-
readonly cause?: string | undefined;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const levelTag: Partial<Record<LogLevel.LogLevel, string>> = {
|
|
25
|
-
Trace: "trace",
|
|
26
|
-
Debug: "debug",
|
|
27
|
-
Warn: "WARN",
|
|
28
|
-
Error: "ERROR",
|
|
29
|
-
Fatal: "FATAL",
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
const asText = (value: unknown): string => {
|
|
33
|
-
if (Predicate.isString(value)) return value;
|
|
34
|
-
try {
|
|
35
|
-
return JSON.stringify(value) ?? String(value);
|
|
36
|
-
} catch {
|
|
37
|
-
return String(value);
|
|
38
|
-
}
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
const annotationText = (annotations: Readonly<Record<string, unknown>>): string =>
|
|
42
|
-
Object.entries(annotations)
|
|
43
|
-
// Dotted keys are the OTel-convention duplicates of the camelCase
|
|
44
|
-
// annotations; one copy per line is enough for a human console.
|
|
45
|
-
.filter(([key]) => !key.includes("."))
|
|
46
|
-
.map(([key, value]) => `${key}=${asText(value).slice(0, 120)}`)
|
|
47
|
-
.join(" ");
|
|
48
|
-
|
|
49
|
-
/** Known engine telemetry messages, rendered as short progress lines. */
|
|
50
|
-
const telemetryLine = (
|
|
51
|
-
message: string,
|
|
52
|
-
annotations: Readonly<Record<string, unknown>>,
|
|
53
|
-
): string | undefined => {
|
|
54
|
-
const toolName = asText(annotations["toolName"] ?? "tool");
|
|
55
|
-
switch (message) {
|
|
56
|
-
case "agent tool execution completed":
|
|
57
|
-
return `✓ tool ${toolName}`;
|
|
58
|
-
case "agent tool execution failed":
|
|
59
|
-
return `✗ tool ${toolName} failed`;
|
|
60
|
-
case "agent tool handler started":
|
|
61
|
-
case "agent programmatic tool handler started":
|
|
62
|
-
return `→ tool ${toolName}`;
|
|
63
|
-
case "agent model call started":
|
|
64
|
-
return `→ model call`;
|
|
65
|
-
case "agent run started":
|
|
66
|
-
return `→ agent ${asText(annotations["agentId"] ?? "run")} started`;
|
|
67
|
-
default:
|
|
68
|
-
return undefined;
|
|
69
|
-
}
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
/** Render one log record as a single compact console line. */
|
|
73
|
-
export const formatCompactLogLine = (record: CompactLogRecord): string => {
|
|
74
|
-
const time = record.date.toISOString().slice(11, 19);
|
|
75
|
-
const messages = Array.isArray(record.message) ? record.message : [record.message];
|
|
76
|
-
const messageText = messages.map(asText).join(" ");
|
|
77
|
-
const mapped =
|
|
78
|
-
messages.length === 1 && Predicate.isString(messages[0])
|
|
79
|
-
? telemetryLine(messages[0], record.annotations)
|
|
80
|
-
: undefined;
|
|
81
|
-
const tag = levelTag[record.logLevel];
|
|
82
|
-
let line: string;
|
|
83
|
-
if (mapped !== undefined) {
|
|
84
|
-
line = `[${time}] ${tag === undefined ? "" : `${tag} `}${mapped}`;
|
|
85
|
-
} else {
|
|
86
|
-
const isSevere =
|
|
87
|
-
record.logLevel === "Warn" || record.logLevel === "Error" || record.logLevel === "Fatal";
|
|
88
|
-
const annotations = isSevere ? annotationText(record.annotations) : "";
|
|
89
|
-
line = `[${time}] ${tag === undefined ? "" : `${tag} `}${messageText}${
|
|
90
|
-
annotations === "" ? "" : ` · ${annotations}`
|
|
91
|
-
}`;
|
|
92
|
-
}
|
|
93
|
-
return record.cause === undefined ? line : `${line}\n${record.cause}`;
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
/** The compact Logger; annotations come from the emitting fiber. */
|
|
97
|
-
export const compactReviewLogger: Logger.Logger<unknown, string> = Logger.make((options) =>
|
|
98
|
-
formatCompactLogLine({
|
|
99
|
-
date: options.date,
|
|
100
|
-
logLevel: options.logLevel,
|
|
101
|
-
message: options.message,
|
|
102
|
-
annotations: options.fiber.getRef(References.CurrentLogAnnotations),
|
|
103
|
-
cause: options.cause.reasons.length > 0 ? Cause.pretty(options.cause) : undefined,
|
|
104
|
-
}),
|
|
105
|
-
);
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Install the compact console logger and the minimum level for one host run.
|
|
109
|
-
* PR_REVIEW_LOG_LEVEL widens visibility (e.g. "Debug" shows the engine's
|
|
110
|
-
* per-turn and per-handler telemetry); an unknown value fails loudly like
|
|
111
|
-
* every other configuration fault.
|
|
112
|
-
*/
|
|
113
|
-
export const compactReviewLoggingLayer: Layer.Layer<never, Config.ConfigError> = Layer.unwrap(
|
|
114
|
-
Effect.gen(function* () {
|
|
115
|
-
const level = yield* Config.literals(
|
|
116
|
-
["All", "Trace", "Debug", "Info", "Warn", "Error"],
|
|
117
|
-
"PR_REVIEW_LOG_LEVEL",
|
|
118
|
-
).pipe(Config.withDefault<LogLevel.LogLevel>("Info"));
|
|
119
|
-
return Layer.merge(
|
|
120
|
-
Logger.layer([Logger.withLeveledConsole(compactReviewLogger)]),
|
|
121
|
-
Layer.succeed(References.MinimumLogLevel, level),
|
|
122
|
-
);
|
|
123
|
-
}),
|
|
124
|
-
);
|
package/src/internal/profiles.ts
DELETED
|
@@ -1,91 +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: bounded host-scheduled child runs per invocation. */
|
|
42
|
-
deploymentClass: Schema.Literal("E"),
|
|
43
|
-
/** Every model-callable surface is evidence-only; children expose no tools. */
|
|
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
|
-
/** Host code schedules every pass from the deterministic plan; no coordinator model. */
|
|
50
|
-
hostScheduledPasses: Schema.Literal(true),
|
|
51
|
-
/** A failed pass is retried once, then reported and carried as unreviewed scope. */
|
|
52
|
-
failedPassesRetriedOnceThenCarried: Schema.Literal(true),
|
|
53
|
-
/** Risk categories and required specialist passes are pure host policy. */
|
|
54
|
-
hostOwnedRiskClassification: Schema.Literal(true),
|
|
55
|
-
/** Every host-classified high-risk unit receives a fresh specialist pass. */
|
|
56
|
-
redundantHighRiskDiscovery: Schema.Literal(true),
|
|
57
|
-
/** Only exact candidates confirmed by a fresh verifier child may publish. */
|
|
58
|
-
independentCandidateVerification: Schema.Literal(true),
|
|
59
|
-
/** No bounded model pipeline proves that a pull request is defect-free. */
|
|
60
|
-
defectAbsenceProven: Schema.Literal(false),
|
|
61
|
-
/** The live profile is env-gated out of every ordinary test gate. */
|
|
62
|
-
liveProfileOptIn: Schema.Literal(true),
|
|
63
|
-
/** Never claimed at any phase (DUR-003). */
|
|
64
|
-
exactlyOnceExternalEffects: Schema.Literal(false),
|
|
65
|
-
}) {}
|
|
66
|
-
|
|
67
|
-
export const fanOutReviewerProfile = FanOutReviewerProfile.make({
|
|
68
|
-
deploymentClass: "E",
|
|
69
|
-
readOnlyToolSurface: true,
|
|
70
|
-
publicationOutsideAgentLoop: true,
|
|
71
|
-
anchorsValidatedBeforePublication: true,
|
|
72
|
-
hostScheduledPasses: true,
|
|
73
|
-
failedPassesRetriedOnceThenCarried: true,
|
|
74
|
-
hostOwnedRiskClassification: true,
|
|
75
|
-
redundantHighRiskDiscovery: true,
|
|
76
|
-
independentCandidateVerification: true,
|
|
77
|
-
defectAbsenceProven: false,
|
|
78
|
-
liveProfileOptIn: true,
|
|
79
|
-
exactlyOnceExternalEffects: false,
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
export const LIVE_GATE_ENV = "EFFECT_AGENT_LIVE";
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* `EFFECT_AGENT_LIVE=1` plus the named credential is the only enabling
|
|
86
|
-
* combination for live (network, billed) profiles.
|
|
87
|
-
*/
|
|
88
|
-
export const liveProfileEnabled = (
|
|
89
|
-
env: Record<string, string | undefined>,
|
|
90
|
-
credentialEnv: string,
|
|
91
|
-
): boolean => env[LIVE_GATE_ENV] === "1" && (env[credentialEnv] ?? "") !== "";
|