@almightygpt/core 0.3.0 → 0.5.1
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/dist/adapters/claude.d.ts +1 -1
- package/dist/adapters/claude.d.ts.map +1 -1
- package/dist/adapters/claude.js +46 -6
- package/dist/adapters/claude.js.map +1 -1
- package/dist/adapters/gemini.d.ts +1 -1
- package/dist/adapters/gemini.d.ts.map +1 -1
- package/dist/adapters/gemini.js.map +1 -1
- package/dist/adapters/mock.d.ts +1 -0
- package/dist/adapters/mock.d.ts.map +1 -1
- package/dist/adapters/mock.js +1 -0
- package/dist/adapters/mock.js.map +1 -1
- package/dist/adapters/openai.d.ts +1 -1
- package/dist/adapters/openai.d.ts.map +1 -1
- package/dist/adapters/openai.js +20 -5
- package/dist/adapters/openai.js.map +1 -1
- package/dist/adapters/types.d.ts +20 -2
- package/dist/adapters/types.d.ts.map +1 -1
- package/dist/adapters/types.js.map +1 -1
- package/dist/git/status.d.ts.map +1 -1
- package/dist/git/status.js +18 -5
- package/dist/git/status.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/review/diff-filter.d.ts +37 -0
- package/dist/review/diff-filter.d.ts.map +1 -0
- package/dist/review/diff-filter.js +144 -0
- package/dist/review/diff-filter.js.map +1 -0
- package/dist/review/events.d.ts +2 -0
- package/dist/review/events.d.ts.map +1 -1
- package/dist/review/run-diff-review.d.ts +2 -0
- package/dist/review/run-diff-review.d.ts.map +1 -1
- package/dist/review/run-diff-review.js +34 -8
- package/dist/review/run-diff-review.js.map +1 -1
- package/dist/review/run-worker-reviewer.d.ts.map +1 -1
- package/dist/review/run-worker-reviewer.js +43 -10
- package/dist/review/run-worker-reviewer.js.map +1 -1
- package/dist/review/write.d.ts +15 -0
- package/dist/review/write.d.ts.map +1 -1
- package/dist/review/write.js +29 -0
- package/dist/review/write.js.map +1 -1
- package/dist/runs/types.d.ts +6 -0
- package/dist/runs/types.d.ts.map +1 -1
- package/package.json +2 -1
- package/src/adapters/claude.ts +59 -8
- package/src/adapters/gemini.ts +1 -1
- package/src/adapters/mock.ts +1 -0
- package/src/adapters/openai.ts +35 -7
- package/src/adapters/types.ts +20 -2
- package/src/git/status.ts +21 -5
- package/src/index.ts +3 -1
- package/src/review/diff-filter.ts +190 -0
- package/src/review/events.ts +2 -0
- package/src/review/run-diff-review.ts +46 -8
- package/src/review/run-worker-reviewer.ts +53 -10
- package/src/review/write.ts +42 -0
- package/src/runs/types.ts +6 -0
|
@@ -37,12 +37,16 @@ import {
|
|
|
37
37
|
} from "../runs/folder.js";
|
|
38
38
|
import type { RunMetadata } from "../runs/types.js";
|
|
39
39
|
import { collectGitDiff } from "./diff.js";
|
|
40
|
+
import { filterDiffByIgnoreLists } from "./diff-filter.js";
|
|
40
41
|
import { assembleMemory } from "./memory.js";
|
|
41
42
|
import {
|
|
42
43
|
buildReviewerSystemFraming,
|
|
43
44
|
buildReviewerUserMessage,
|
|
44
45
|
} from "./prompts.js";
|
|
45
|
-
import {
|
|
46
|
+
import {
|
|
47
|
+
preflightReviewFileCollision,
|
|
48
|
+
writeHumanReviewFile,
|
|
49
|
+
} from "./write.js";
|
|
46
50
|
import { BudgetTracker, BudgetExceededError } from "./budget.js";
|
|
47
51
|
|
|
48
52
|
export interface DiffReviewOptions {
|
|
@@ -63,6 +67,8 @@ export interface DiffReviewResult {
|
|
|
63
67
|
provider: string;
|
|
64
68
|
modelUsed: string;
|
|
65
69
|
tokensIn: number;
|
|
70
|
+
/** Portion of tokensIn served from prompt cache (0 if no cache hit). */
|
|
71
|
+
cachedTokensIn: number;
|
|
66
72
|
tokensOut: number;
|
|
67
73
|
costUsd: number;
|
|
68
74
|
latencyMs: number;
|
|
@@ -109,6 +115,16 @@ export async function runDiffReview(
|
|
|
109
115
|
);
|
|
110
116
|
}
|
|
111
117
|
|
|
118
|
+
// Preflight the review-file collision BEFORE any paid adapter call.
|
|
119
|
+
// (Codex review v0.5: previously the check ran post-adapter, burning API
|
|
120
|
+
// money on duplicate-topic runs that were going to fail anyway.)
|
|
121
|
+
await preflightReviewFileCollision(
|
|
122
|
+
opts.repoRoot,
|
|
123
|
+
config.reviewsDir,
|
|
124
|
+
opts.topic,
|
|
125
|
+
opts.force ?? false,
|
|
126
|
+
);
|
|
127
|
+
|
|
112
128
|
// Create run folder up front so partial failures still leave an artifact.
|
|
113
129
|
const runFolder = await createRunFolder({
|
|
114
130
|
repoRoot: opts.repoRoot,
|
|
@@ -155,14 +171,29 @@ export async function runDiffReview(
|
|
|
155
171
|
);
|
|
156
172
|
}
|
|
157
173
|
|
|
174
|
+
// Codex review v0.5 P1 #2: filter ignored files BEFORE secret redaction
|
|
175
|
+
// and BEFORE the provider call. Honors .almightyignore + .gitignore +
|
|
176
|
+
// config.context.exclude. Redaction still runs on the survivors as
|
|
177
|
+
// defense-in-depth.
|
|
178
|
+
const filtered = await filterDiffByIgnoreLists({
|
|
179
|
+
repoRoot: opts.repoRoot,
|
|
180
|
+
diffText: diffResult.diff,
|
|
181
|
+
files: diffResult.files,
|
|
182
|
+
configExclude: config.context.exclude,
|
|
183
|
+
});
|
|
184
|
+
|
|
158
185
|
const redaction = config.security.redactSecrets
|
|
159
|
-
? redactSecrets(
|
|
160
|
-
: { text:
|
|
186
|
+
? redactSecrets(filtered.filteredDiff)
|
|
187
|
+
: { text: filtered.filteredDiff, redactions: [], totalCount: 0 };
|
|
161
188
|
|
|
162
189
|
const manifest = buildContextManifest({
|
|
163
190
|
inputSource: opts.range ? "diff-range" : "diff",
|
|
164
|
-
filesIncluded:
|
|
165
|
-
filesSkipped:
|
|
191
|
+
filesIncluded: filtered.filesIncluded.map((p) => ({ path: p, bytes: 0 })),
|
|
192
|
+
filesSkipped: filtered.filesSkipped.map((s) => ({
|
|
193
|
+
path: s.path,
|
|
194
|
+
bytes: 0,
|
|
195
|
+
skippedReason: s.reason,
|
|
196
|
+
})),
|
|
166
197
|
diffText: redaction.text,
|
|
167
198
|
redaction: {
|
|
168
199
|
enabled: config.security.redactSecrets,
|
|
@@ -182,15 +213,18 @@ export async function runDiffReview(
|
|
|
182
213
|
const userMessage = buildReviewerUserMessage({
|
|
183
214
|
topic: opts.topic,
|
|
184
215
|
diff: redaction.text,
|
|
185
|
-
files:
|
|
216
|
+
files: filtered.filesIncluded,
|
|
186
217
|
});
|
|
187
218
|
|
|
188
219
|
const budget = new BudgetTracker(config.budget);
|
|
189
220
|
const estimatedTokensIn = Math.ceil(
|
|
190
221
|
(systemPrompt.length + userMessage.length) / 4,
|
|
191
222
|
);
|
|
223
|
+
// Codex review v0.5 P2 #5: previously hardcoded "gpt-4o" here, which
|
|
224
|
+
// could block a cheap Gemini run on an OpenAI cost estimate. Use the
|
|
225
|
+
// actual adapter's default model so the preflight matches reality.
|
|
192
226
|
budget.preflightCheck({
|
|
193
|
-
model:
|
|
227
|
+
model: adapter.defaultModel,
|
|
194
228
|
estimatedTokensIn,
|
|
195
229
|
maxOutputTokens: 4096,
|
|
196
230
|
});
|
|
@@ -247,6 +281,9 @@ export async function runDiffReview(
|
|
|
247
281
|
provider: adapter.provider,
|
|
248
282
|
model: adapterOut.modelUsed,
|
|
249
283
|
tokensIn: adapterOut.tokensIn,
|
|
284
|
+
...(adapterOut.cachedTokensIn !== undefined && adapterOut.cachedTokensIn > 0
|
|
285
|
+
? { cachedTokensIn: adapterOut.cachedTokensIn }
|
|
286
|
+
: {}),
|
|
250
287
|
tokensOut: adapterOut.tokensOut,
|
|
251
288
|
costUsd: adapterOut.costUsd,
|
|
252
289
|
latencyMs: adapterOut.latencyMs,
|
|
@@ -268,11 +305,12 @@ export async function runDiffReview(
|
|
|
268
305
|
provider: adapter.provider,
|
|
269
306
|
modelUsed: adapterOut.modelUsed,
|
|
270
307
|
tokensIn: adapterOut.tokensIn,
|
|
308
|
+
cachedTokensIn: adapterOut.cachedTokensIn ?? 0,
|
|
271
309
|
tokensOut: adapterOut.tokensOut,
|
|
272
310
|
costUsd: adapterOut.costUsd,
|
|
273
311
|
latencyMs: adapterOut.latencyMs,
|
|
274
312
|
diffEmpty: diffResult.empty,
|
|
275
|
-
filesReviewed:
|
|
313
|
+
filesReviewed: filtered.filesIncluded,
|
|
276
314
|
memorySources: memory.sources,
|
|
277
315
|
memoryMissing: memory.missing,
|
|
278
316
|
runId: runFolder.id,
|
|
@@ -45,6 +45,7 @@ import {
|
|
|
45
45
|
} from "../runs/folder.js";
|
|
46
46
|
import type { AgentMetrics, RunMetadata } from "../runs/types.js";
|
|
47
47
|
import { collectGitDiff } from "./diff.js";
|
|
48
|
+
import { filterDiffByIgnoreLists } from "./diff-filter.js";
|
|
48
49
|
import { assembleMemory } from "./memory.js";
|
|
49
50
|
import {
|
|
50
51
|
buildReviewerSystemFraming,
|
|
@@ -52,7 +53,10 @@ import {
|
|
|
52
53
|
buildWorkerSystemFraming,
|
|
53
54
|
buildWorkerUserMessage,
|
|
54
55
|
} from "./prompts.js";
|
|
55
|
-
import {
|
|
56
|
+
import {
|
|
57
|
+
preflightReviewFileCollision,
|
|
58
|
+
writeHumanReviewFile,
|
|
59
|
+
} from "./write.js";
|
|
56
60
|
import { BudgetTracker, BudgetExceededError } from "./budget.js";
|
|
57
61
|
import type { ReviewEventHandler } from "./events.js";
|
|
58
62
|
|
|
@@ -153,6 +157,16 @@ export async function runWorkerReviewerReview(
|
|
|
153
157
|
? `Worker and Reviewer are both ${workerCfg.provider}. Cross-vendor pairing catches more issues.`
|
|
154
158
|
: undefined;
|
|
155
159
|
|
|
160
|
+
// Preflight the review-file collision BEFORE Worker or Reviewer fires.
|
|
161
|
+
// (Codex review v0.5: previously the check ran post-adapter, burning two
|
|
162
|
+
// adapter calls of API money on duplicate-topic runs.)
|
|
163
|
+
await preflightReviewFileCollision(
|
|
164
|
+
opts.repoRoot,
|
|
165
|
+
config.reviewsDir,
|
|
166
|
+
opts.topic,
|
|
167
|
+
opts.force ?? false,
|
|
168
|
+
);
|
|
169
|
+
|
|
156
170
|
const runFolder = await createRunFolder({
|
|
157
171
|
repoRoot: opts.repoRoot,
|
|
158
172
|
runsDir: config.runsDir,
|
|
@@ -207,9 +221,19 @@ export async function runWorkerReviewerReview(
|
|
|
207
221
|
);
|
|
208
222
|
}
|
|
209
223
|
|
|
224
|
+
// Codex review v0.5 P1 #2: filter ignored files BEFORE redaction and
|
|
225
|
+
// BEFORE either adapter call. Honors .almightyignore + .gitignore +
|
|
226
|
+
// config.context.exclude.
|
|
227
|
+
const filtered = await filterDiffByIgnoreLists({
|
|
228
|
+
repoRoot: opts.repoRoot,
|
|
229
|
+
diffText: diffResult.diff,
|
|
230
|
+
files: diffResult.files,
|
|
231
|
+
configExclude: config.context.exclude,
|
|
232
|
+
});
|
|
233
|
+
|
|
210
234
|
const redaction = config.security.redactSecrets
|
|
211
|
-
? redactSecrets(
|
|
212
|
-
: { text:
|
|
235
|
+
? redactSecrets(filtered.filteredDiff)
|
|
236
|
+
: { text: filtered.filteredDiff, redactions: [], totalCount: 0 };
|
|
213
237
|
emit({
|
|
214
238
|
type: "redaction_complete",
|
|
215
239
|
totalCount: redaction.totalCount,
|
|
@@ -218,8 +242,12 @@ export async function runWorkerReviewerReview(
|
|
|
218
242
|
|
|
219
243
|
const manifest = buildContextManifest({
|
|
220
244
|
inputSource: opts.range ? "diff-range" : "diff",
|
|
221
|
-
filesIncluded:
|
|
222
|
-
filesSkipped:
|
|
245
|
+
filesIncluded: filtered.filesIncluded.map((p) => ({ path: p, bytes: 0 })),
|
|
246
|
+
filesSkipped: filtered.filesSkipped.map((s) => ({
|
|
247
|
+
path: s.path,
|
|
248
|
+
bytes: 0,
|
|
249
|
+
skippedReason: s.reason,
|
|
250
|
+
})),
|
|
223
251
|
diffText: redaction.text,
|
|
224
252
|
redaction: {
|
|
225
253
|
enabled: config.security.redactSecrets,
|
|
@@ -245,11 +273,14 @@ export async function runWorkerReviewerReview(
|
|
|
245
273
|
const workerUser = buildWorkerUserMessage({
|
|
246
274
|
topic: opts.topic,
|
|
247
275
|
diff: redaction.text,
|
|
248
|
-
files:
|
|
276
|
+
files: filtered.filesIncluded,
|
|
249
277
|
});
|
|
250
278
|
|
|
279
|
+
// Codex review v0.5 P2 #5: use the actual adapter's default model
|
|
280
|
+
// for the preflight estimate so a cheap Gemini Worker isn't blocked
|
|
281
|
+
// by an OpenAI cost estimate.
|
|
251
282
|
budget.preflightCheck({
|
|
252
|
-
model:
|
|
283
|
+
model: workerAdapter.defaultModel,
|
|
253
284
|
estimatedTokensIn: Math.ceil(
|
|
254
285
|
(workerSystem.length + workerUser.length) / 4,
|
|
255
286
|
),
|
|
@@ -281,6 +312,9 @@ export async function runWorkerReviewerReview(
|
|
|
281
312
|
model: workerOut.modelUsed,
|
|
282
313
|
outputPath: `${runFolder.relPath}/outputs/worker.md`,
|
|
283
314
|
tokensIn: workerOut.tokensIn,
|
|
315
|
+
...(workerOut.cachedTokensIn !== undefined && workerOut.cachedTokensIn > 0
|
|
316
|
+
? { cachedTokensIn: workerOut.cachedTokensIn }
|
|
317
|
+
: {}),
|
|
284
318
|
tokensOut: workerOut.tokensOut,
|
|
285
319
|
costUsd: workerOut.costUsd,
|
|
286
320
|
latencyMs: workerOut.latencyMs,
|
|
@@ -296,14 +330,14 @@ export async function runWorkerReviewerReview(
|
|
|
296
330
|
const reviewerUser = buildReviewerOfWorkerUserMessage({
|
|
297
331
|
topic: opts.topic,
|
|
298
332
|
diff: redaction.text,
|
|
299
|
-
files:
|
|
333
|
+
files: filtered.filesIncluded,
|
|
300
334
|
workerOutput: workerOut.content,
|
|
301
335
|
workerAgent: workerName,
|
|
302
336
|
workerProvider: workerAdapter.provider,
|
|
303
337
|
});
|
|
304
338
|
|
|
305
339
|
budget.preflightCheck({
|
|
306
|
-
model:
|
|
340
|
+
model: reviewerAdapter.defaultModel,
|
|
307
341
|
estimatedTokensIn: Math.ceil(
|
|
308
342
|
(reviewerSystem.length + reviewerUser.length) / 4,
|
|
309
343
|
),
|
|
@@ -335,6 +369,9 @@ export async function runWorkerReviewerReview(
|
|
|
335
369
|
model: reviewerOut.modelUsed,
|
|
336
370
|
outputPath: `${runFolder.relPath}/outputs/reviewer.md`,
|
|
337
371
|
tokensIn: reviewerOut.tokensIn,
|
|
372
|
+
...(reviewerOut.cachedTokensIn !== undefined && reviewerOut.cachedTokensIn > 0
|
|
373
|
+
? { cachedTokensIn: reviewerOut.cachedTokensIn }
|
|
374
|
+
: {}),
|
|
338
375
|
tokensOut: reviewerOut.tokensOut,
|
|
339
376
|
costUsd: reviewerOut.costUsd,
|
|
340
377
|
latencyMs: reviewerOut.latencyMs,
|
|
@@ -390,6 +427,9 @@ export async function runWorkerReviewerReview(
|
|
|
390
427
|
provider: workerAdapter.provider,
|
|
391
428
|
model: workerOut.modelUsed,
|
|
392
429
|
tokensIn: workerOut.tokensIn,
|
|
430
|
+
...(workerOut.cachedTokensIn !== undefined && workerOut.cachedTokensIn > 0
|
|
431
|
+
? { cachedTokensIn: workerOut.cachedTokensIn }
|
|
432
|
+
: {}),
|
|
393
433
|
tokensOut: workerOut.tokensOut,
|
|
394
434
|
costUsd: workerOut.costUsd,
|
|
395
435
|
latencyMs: workerOut.latencyMs,
|
|
@@ -400,6 +440,9 @@ export async function runWorkerReviewerReview(
|
|
|
400
440
|
provider: reviewerAdapter.provider,
|
|
401
441
|
model: reviewerOut.modelUsed,
|
|
402
442
|
tokensIn: reviewerOut.tokensIn,
|
|
443
|
+
...(reviewerOut.cachedTokensIn !== undefined && reviewerOut.cachedTokensIn > 0
|
|
444
|
+
? { cachedTokensIn: reviewerOut.cachedTokensIn }
|
|
445
|
+
: {}),
|
|
403
446
|
tokensOut: reviewerOut.tokensOut,
|
|
404
447
|
costUsd: reviewerOut.costUsd,
|
|
405
448
|
latencyMs: reviewerOut.latencyMs,
|
|
@@ -442,7 +485,7 @@ export async function runWorkerReviewerReview(
|
|
|
442
485
|
},
|
|
443
486
|
metrics,
|
|
444
487
|
totals,
|
|
445
|
-
filesReviewed:
|
|
488
|
+
filesReviewed: filtered.filesIncluded,
|
|
446
489
|
redactionsTotal: redaction.totalCount,
|
|
447
490
|
memoryMissing: [
|
|
448
491
|
...new Set([...workerMemory.missing, ...reviewerMemory.missing]),
|
package/src/review/write.ts
CHANGED
|
@@ -86,6 +86,48 @@ export async function writeHumanReviewFile(
|
|
|
86
86
|
return { path: relPath, bytes: content.length };
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Compute the review file path for a topic without writing anything. Used
|
|
91
|
+
* by the review pipelines to **preflight** collisions *before* any paid
|
|
92
|
+
* adapter call. Same sanitization rules as writeHumanReviewFile.
|
|
93
|
+
*/
|
|
94
|
+
export function reviewFilePathFor(
|
|
95
|
+
reviewsDir: string,
|
|
96
|
+
topic: string,
|
|
97
|
+
): string {
|
|
98
|
+
return join(reviewsDir, `${sanitizeTopic(topic)}.md`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Preflight check: throws ReviewFileExistsError if the topic's review file
|
|
103
|
+
* already exists and `force` isn't set. Surfaces the error BEFORE any
|
|
104
|
+
* adapter is invoked so the user doesn't spend API money discovering an
|
|
105
|
+
* easily-avoidable collision.
|
|
106
|
+
*
|
|
107
|
+
* Returns the (relative) path so the caller doesn't have to recompute it.
|
|
108
|
+
*/
|
|
109
|
+
export async function preflightReviewFileCollision(
|
|
110
|
+
repoRoot: string,
|
|
111
|
+
reviewsDir: string,
|
|
112
|
+
topic: string,
|
|
113
|
+
force = false,
|
|
114
|
+
): Promise<string> {
|
|
115
|
+
const relPath = reviewFilePathFor(reviewsDir, topic);
|
|
116
|
+
const absPath = join(repoRoot, relPath);
|
|
117
|
+
if (existsSync(absPath) && !force) {
|
|
118
|
+
// Also run the git-status check to give a richer dirty-file message
|
|
119
|
+
// when applicable; falls through to the existence error below.
|
|
120
|
+
await assertSafeToWrite(repoRoot, relPath, false);
|
|
121
|
+
throw new ReviewFileExistsError(
|
|
122
|
+
`Refusing to overwrite ${relPath}. Pass --force to overwrite (the ` +
|
|
123
|
+
`previous version remains in git history). Alternatively pick a ` +
|
|
124
|
+
`different --topic to keep both reviews.`,
|
|
125
|
+
relPath,
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
return relPath;
|
|
129
|
+
}
|
|
130
|
+
|
|
89
131
|
function sanitizeTopic(topic: string): string {
|
|
90
132
|
// Allow letters, digits, dot, dash, underscore. Replace everything else
|
|
91
133
|
// with a single dash. Trim leading/trailing dashes. Lowercase.
|
package/src/runs/types.ts
CHANGED
|
@@ -24,6 +24,12 @@ export interface AgentMetrics {
|
|
|
24
24
|
provider: string;
|
|
25
25
|
model: string;
|
|
26
26
|
tokensIn: number;
|
|
27
|
+
/**
|
|
28
|
+
* Portion of tokensIn served from the provider's prompt cache (Anthropic
|
|
29
|
+
* cache_read, OpenAI prompt_tokens_details.cached_tokens). Always less
|
|
30
|
+
* than or equal to tokensIn. Zero when caching didn't trigger.
|
|
31
|
+
*/
|
|
32
|
+
cachedTokensIn?: number;
|
|
27
33
|
tokensOut: number;
|
|
28
34
|
costUsd: number;
|
|
29
35
|
latencyMs: number;
|