@counterfact-labs/cli 0.2.7 → 0.2.10
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/bundle/client-release.json +1 -1
- package/dist/adapters/analysis-results.js +87 -0
- package/dist/adapters/combined-results.js +27 -0
- package/dist/adapters/entry-files.js +5 -2
- package/dist/adapters/entry-packing.js +161 -0
- package/dist/adapters/entry-selection.js +13 -1
- package/dist/adapters/files.js +110 -5
- package/dist/adapters/git.js +10 -1
- package/dist/adapters/release.js +4 -1
- package/dist/adapters/results.js +9 -2
- package/dist/adapters/selection.js +19 -9
- package/dist/adapters/settings.js +37 -6
- package/dist/analysis-core/analysis/code-agents/skipped-files.js +1 -1
- package/dist/analysis-core/analysis/file-analysis-plan.js +1 -0
- package/dist/analysis-core/analysis/file-dependencies.js +1 -0
- package/dist/analysis-core/analysis/finding-activity-switch.js +1 -0
- package/dist/analysis-core/analysis/finding-identity.js +1 -0
- package/dist/analysis-core/analysis/finding-quality/policy.js +1 -0
- package/dist/analysis-core/analysis/folder-analysis-plan.js +1 -0
- package/dist/analysis-core/analysis/passes/full-file-passes.js +1 -0
- package/dist/analysis-core/analysis/project-architecture.js +1 -0
- package/dist/analysis-core/analysis/schemas.js +1 -1
- package/dist/analysis-core/chunks/2CWULBP2.js +1 -0
- package/dist/analysis-core/chunks/2M55DR2J.js +35 -0
- package/dist/analysis-core/chunks/34NVU26H.js +1 -0
- package/dist/analysis-core/chunks/{RNTMH4MU.js → 5BTATES5.js} +1 -1
- package/dist/analysis-core/chunks/5UWDUYHF.js +1 -0
- package/dist/analysis-core/chunks/{6RWEWBHK.js → 5VQ4VYIP.js} +1 -1
- package/dist/analysis-core/chunks/66JRJPJC.js +1 -0
- package/dist/analysis-core/chunks/6JTHFDH3.js +1 -0
- package/dist/analysis-core/chunks/6VHZNBII.js +19 -0
- package/dist/analysis-core/chunks/6YCABS5T.js +1 -0
- package/dist/analysis-core/chunks/C77LGCKF.js +1 -0
- package/dist/analysis-core/chunks/CNQZLSAP.js +1 -0
- package/dist/analysis-core/chunks/DHVEGIU6.js +1 -0
- package/dist/analysis-core/chunks/HCPO6QPU.js +1 -0
- package/dist/analysis-core/chunks/I4F56VMG.js +1 -0
- package/dist/analysis-core/chunks/INVTU73D.js +1 -0
- package/dist/analysis-core/chunks/{6B2HILSY.js → JAPVLU4Z.js} +1 -1
- package/dist/analysis-core/chunks/KBTM5MBC.js +14 -0
- package/dist/analysis-core/chunks/LFJQ4YLP.js +3 -0
- package/dist/analysis-core/chunks/M5YWJY7P.js +1 -0
- package/dist/analysis-core/chunks/MKYLK6DB.js +1 -0
- package/dist/analysis-core/chunks/N2L4JMJE.js +1 -0
- package/dist/analysis-core/chunks/NCW5LJ5O.js +6 -0
- package/dist/analysis-core/chunks/P253B344.js +7 -0
- package/dist/analysis-core/chunks/PBHHWFR6.js +1 -0
- package/dist/analysis-core/chunks/Q5WR6B2H.js +2 -0
- package/dist/analysis-core/chunks/QPIBHTON.js +67 -0
- package/dist/analysis-core/chunks/QVXARKP7.js +1 -0
- package/dist/analysis-core/chunks/SJESFYBA.js +10 -0
- package/dist/analysis-core/chunks/SP6XFHNH.js +1 -0
- package/dist/analysis-core/chunks/TA2NIQAY.js +1 -0
- package/dist/analysis-core/chunks/UKX3A6AX.js +1 -0
- package/dist/analysis-core/chunks/VC3PYT2B.js +1 -0
- package/dist/analysis-core/chunks/VDUFPTDD.js +1 -0
- package/dist/analysis-core/chunks/VWRFSVLI.js +1 -0
- package/dist/analysis-core/chunks/VX6LYNXV.js +1 -0
- package/dist/analysis-core/chunks/W73WF226.js +1 -0
- package/dist/analysis-core/chunks/{FBTOOUPL.js → XVGLY4EO.js} +1 -1
- package/dist/analysis-core/chunks/YWL52FVW.js +2 -0
- package/dist/analysis-core/contracts/analysis-selection.js +1 -1
- package/dist/analysis-core/contracts/analysis-size.js +1 -0
- package/dist/analysis-core/contracts/execution-request.js +1 -0
- package/dist/analysis-core/contracts/intake-finding.js +1 -0
- package/dist/analysis-core/contracts/model-call-identity.js +1 -0
- package/dist/analysis-core/contracts/relay-analysis.js +1 -1
- package/dist/analysis-core/contracts/requests.js +1 -1
- package/dist/analysis-core/conventions/analysis-fit.js +1 -1
- package/dist/analysis-core/conventions/file-admission.js +1 -1
- package/dist/analysis-core/conventions/read-limits.js +1 -0
- package/dist/analysis-core/import/calls/modules.js +1 -1
- package/dist/analysis-core/import/entry-selection.js +1 -0
- package/dist/analysis-core/import/outside-loader-sources.js +1 -1
- package/dist/analysis-core/index.js +3 -3
- package/dist/analysis-core/requests/answer-formats.js +1 -1
- package/dist/analysis-core/requests/local-plan.js +1 -1
- package/dist/analysis-core/requests/planned-limits.js +1 -0
- package/dist/analysis-core/requests/registry.js +1 -1
- package/dist/analysis-core/requests/template-version.js +1 -1
- package/dist/analysis-core/requests/tools.js +1 -1
- package/dist/analysis-core/settings.js +1 -1
- package/dist/analysis-core/stages/engine-entry.js +1 -1
- package/dist/bundle/seal/engine.js +1 -1
- package/dist/cli/analysis-selection.js +47 -5
- package/dist/cli/api.js +1 -1
- package/dist/cli/commands/analyze.js +55 -15
- package/dist/cli/commands/skipped.js +65 -0
- package/dist/cli/context.js +2 -1
- package/dist/cli/finding-display.js +102 -7
- package/dist/cli/finding-settings.js +3 -1
- package/dist/cli/intake-payload.js +25 -2
- package/dist/cli/main.js +21 -7
- package/dist/cli/mcp-failure.js +6 -5
- package/dist/cli/mcp.js +134 -28
- package/dist/cli/relay-check.js +103 -23
- package/dist/cli/relay-failure.js +4 -4
- package/dist/cli/relay-preparation.js +47 -9
- package/dist/cli/result-check.js +31 -12
- package/dist/cli/saved-results.js +10 -0
- package/dist/cli/scope-choices.js +3 -3
- package/dist/cli/sign-in.js +2 -2
- package/dist/cli/skipped-files.js +22 -0
- package/dist/cli/summary.js +15 -1
- package/dist/contract/agent-choice.js +25 -2
- package/dist/contract/cli-api-messages.js +3 -0
- package/dist/contract/cli-refusals.js +31 -5
- package/dist/contract/cli-version.js +1 -1
- package/dist/contract/console-url.js +3 -0
- package/dist/contract/console.js +17 -38
- package/dist/contract/engine.js +1 -1
- package/dist/contract/messages.js +4 -3
- package/dist/contract/project-architecture.js +1 -0
- package/dist/contract/scope-choice.js +31 -11
- package/dist/contract/selection-offer.js +5 -8
- package/dist/gatekeeper/cli-api.js +70 -4
- package/dist/gatekeeper/console.js +8 -3
- package/dist/gatekeeper/model.js +5 -23
- package/dist/gatekeeper/trust/policy.js +2 -1
- package/package.json +4 -2
- package/dist/analysis-core/analysis/source-notes/types.js +0 -1
- package/dist/analysis-core/chunks/2PSNOSJ2.js +0 -1
- package/dist/analysis-core/chunks/2PZF54KH.js +0 -10
- package/dist/analysis-core/chunks/6U2IR4XB.js +0 -1
- package/dist/analysis-core/chunks/7NEWZ74F.js +0 -1
- package/dist/analysis-core/chunks/CGCJN73D.js +0 -14
- package/dist/analysis-core/chunks/CWYVUGG4.js +0 -1
- package/dist/analysis-core/chunks/FSLWD2PE.js +0 -1
- package/dist/analysis-core/chunks/GOQVKWP3.js +0 -32
- package/dist/analysis-core/chunks/IHAVDX4L.js +0 -66
- package/dist/analysis-core/chunks/IVULXWQN.js +0 -21
- package/dist/analysis-core/chunks/K2GNDX3W.js +0 -1
- package/dist/analysis-core/chunks/K5FUABWW.js +0 -1
- package/dist/analysis-core/chunks/KGQYR62R.js +0 -7
- package/dist/analysis-core/chunks/KWECQJ6X.js +0 -1
- package/dist/analysis-core/chunks/PY5M22CL.js +0 -1
- package/dist/analysis-core/chunks/PYKAERZZ.js +0 -1
- package/dist/analysis-core/chunks/SLJ6CW7W.js +0 -1
- package/dist/analysis-core/chunks/UEORNVQB.js +0 -1
- package/dist/analysis-core/chunks/WYLIV2P2.js +0 -1
- package/dist/analysis-core/data/finding-types.js +0 -1
- /package/dist/analysis-core/chunks/{SKVEBOGA.js → 33EUZF25.js} +0 -0
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { fileScopeOf } from "../analysis-core/analysis/file-analysis-plan.js";
|
|
1
2
|
import { randomUUID } from "node:crypto";
|
|
2
3
|
import { join, relative, sep } from "node:path";
|
|
3
|
-
import { outsideLoadersEnabled } from "../analysis-core/settings.js";
|
|
4
|
+
import { outsideLoadersEnabled, folderPartsEnabled } from "../analysis-core/settings.js";
|
|
5
|
+
import { localSourcePlan } from "../analysis-core/requests/local-plan.js";
|
|
4
6
|
import { resolveFolderPath } from "../adapters/folder-path.js";
|
|
5
7
|
import { readLocalFolder } from "../adapters/files.js";
|
|
6
8
|
import { snapshotProvenance, sameFileHashes } from "../adapters/snapshot.js";
|
|
@@ -12,6 +14,7 @@ import { COMMITTED_FILES_MESSAGE } from "../contract/cli-refusals.js";
|
|
|
12
14
|
import { INVALID_PROPOSAL_MESSAGE } from "../contract/agent-proposal.js";
|
|
13
15
|
import { selectionStartIdentity } from "../contract/selection-metadata.js";
|
|
14
16
|
import { repositoryFacts } from "./git.js";
|
|
17
|
+
import { proposalWhollyRefused } from "../analysis-core/index.js";
|
|
15
18
|
/** Absolute checkout paths stay local; repository facts own the start's commit. */
|
|
16
19
|
export function buildRelayStartPayload(facts, files) {
|
|
17
20
|
const { remoteUrl, relPath, folderName, branch, commit, dirty } = facts;
|
|
@@ -20,6 +23,47 @@ export function buildRelayStartPayload(facts, files) {
|
|
|
20
23
|
export function committedFilesOnly(settings) {
|
|
21
24
|
return settings.CF_LOCAL_COMMITTED_ONLY === "1";
|
|
22
25
|
}
|
|
26
|
+
/** Every local choice uses the same canonical launch folder and source coordinates. */
|
|
27
|
+
async function launchChoice(choice, folder, sourceBase, cwd) {
|
|
28
|
+
const launch = await resolveFolderPath(cwd), base = sourceBase ?? await resolveFolderPath(folder);
|
|
29
|
+
const launchPath = (path) => relative(launch, join(base, path)).split(sep).join("/") || ".";
|
|
30
|
+
return { ...choice, candidates: choice.candidates.map(candidate => ({ ...candidate,
|
|
31
|
+
folder: launchPath(candidate.folder), reason: launchPath(candidate.reason),
|
|
32
|
+
})) };
|
|
33
|
+
}
|
|
34
|
+
/** Every part must run alone; cost refusals also need a smaller normal call plan. */
|
|
35
|
+
export async function prepareFolderChoice(folder, options) {
|
|
36
|
+
const { settings, signal } = options;
|
|
37
|
+
if (!folderPartsEnabled(settings) || fileScopeOf(settings) !== undefined)
|
|
38
|
+
return undefined;
|
|
39
|
+
if (options.limit === "plan_over_limit" && !options.refusedCalls)
|
|
40
|
+
return undefined;
|
|
41
|
+
signal?.throwIfAborted();
|
|
42
|
+
const read = await readLocalFolder(settings, folder, options.trialAdmission, { planParts: true, captureSnapshot: true });
|
|
43
|
+
const count = (calls) => calls.reduce((sum, call) => sum + call.calls, 0);
|
|
44
|
+
const refusedCount = count(options.refusedCalls ?? []);
|
|
45
|
+
const candidates = [];
|
|
46
|
+
// Finish each validation before starting another walk, so ten choices bound the extra reads.
|
|
47
|
+
for (const part of read.analysisParts ?? []) {
|
|
48
|
+
signal?.throwIfAborted();
|
|
49
|
+
try {
|
|
50
|
+
const scopedSettings = { ...settings, CF_PART_FILES: JSON.stringify(part.files) };
|
|
51
|
+
const source = await readLocalFolder(scopedSettings, folder, options.trialAdmission, { captureSnapshot: true });
|
|
52
|
+
if (source.agentChoice || part.files.some(path => !Object.hasOwn(source.files, path)))
|
|
53
|
+
continue;
|
|
54
|
+
if (options.limit === "plan_over_limit" && count(localSourcePlan(scopedSettings, source).calls) >= refusedCount)
|
|
55
|
+
continue;
|
|
56
|
+
candidates.push({ folder: ".", ...part });
|
|
57
|
+
if (candidates.length === 10)
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
signal?.throwIfAborted();
|
|
65
|
+
return candidates.length ? launchChoice({ kind: "choose_scope", candidates }, folder, read.sourceBase, options.cwd ?? process.cwd()) : undefined;
|
|
66
|
+
}
|
|
23
67
|
/** Recovery can reuse signature validation without reading the folder again. */
|
|
24
68
|
export async function prepareRelayCheck(folder, options, validated = validateClientRelease(options.release, options.settings)) {
|
|
25
69
|
const { bundle, tools } = validated;
|
|
@@ -38,16 +82,10 @@ export async function prepareRelayCheck(folder, options, validated = validateCli
|
|
|
38
82
|
...(earlyFacts ? { expectedRepositoryRoot: earlyFacts.repoRoot ?? null } : {}) });
|
|
39
83
|
},
|
|
40
84
|
});
|
|
41
|
-
if (read.proposalCounts
|
|
85
|
+
if (proposalWhollyRefused(read.proposalCounts))
|
|
42
86
|
throw new Error(INVALID_PROPOSAL_MESSAGE);
|
|
43
87
|
if (read.agentChoice) {
|
|
44
|
-
|
|
45
|
-
const launchPath = (path) => relative(cwd, join(read.sourceBase ?? folder, path)).split(sep).join("/") || ".";
|
|
46
|
-
throw new AgentChoiceError({ ...read.agentChoice,
|
|
47
|
-
candidates: read.agentChoice.candidates.map(candidate => ({
|
|
48
|
-
folder: launchPath(candidate.folder), reason: launchPath(candidate.reason),
|
|
49
|
-
})),
|
|
50
|
-
});
|
|
88
|
+
throw new AgentChoiceError(await launchChoice(read.agentChoice, folder, read.sourceBase, options.cwd ?? process.cwd()));
|
|
51
89
|
}
|
|
52
90
|
const source = { ...read, files: Object.freeze({ ...read.files }) };
|
|
53
91
|
const provenance = await snapshotProvenance(selection?.kind === "file" ? selection.launchRoot : source.sourceBase ?? folder, source.snapshot, { selectionFolder: factsFolder, committedOnly: committedFilesOnly(options.settings) });
|
package/dist/cli/result-check.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { publicFinding, resultCheckFinding, RESULT_CHECK_LIMITS } from "../analysis-core/contracts/
|
|
1
|
+
import { publicFinding, intakeFinding, resultCheckFinding, RESULT_CHECK_LIMITS } from "../analysis-core/contracts/intake-finding.js";
|
|
2
2
|
import { findingSitesInOrder } from "../analysis-core/findings/sites.js";
|
|
3
|
-
import { localFindingSchema } from "../contract/console.js";
|
|
4
3
|
function evidenceQuotes(finding, repoKey) {
|
|
5
4
|
const primary = finding.site ? { repo: finding.siteRepo ?? repoKey, ...finding.site, excerpt: finding.excerpt } : null;
|
|
6
5
|
const sites = findingSitesInOrder(primary, finding.otherSites);
|
|
@@ -36,27 +35,47 @@ export function checkIntakeFindings(findings, context, onInvalidQuote) {
|
|
|
36
35
|
return originals.get(path);
|
|
37
36
|
}
|
|
38
37
|
let quotes = 0, droppedQuotes = 0, invalidQuotes = 0, schemaDroppedFindings = 0, capDroppedFindings = 0;
|
|
38
|
+
function invalidQuote(quote) {
|
|
39
|
+
const source = quote.repo === context.repoKey && typeof quote.path === "string" && Object.hasOwn(context.files, quote.path)
|
|
40
|
+
? original(quote.path) : null;
|
|
41
|
+
const invalid = !quote.text.length || source === null || !source.includes(quote.text);
|
|
42
|
+
if (invalid)
|
|
43
|
+
onInvalidQuote?.({ text: quote.text, source });
|
|
44
|
+
return invalid;
|
|
45
|
+
}
|
|
39
46
|
findings.forEach((finding, index) => {
|
|
40
47
|
const parsed = publicFinding.safeParse(finding);
|
|
48
|
+
let activityQuoteCount = 0;
|
|
49
|
+
if (parsed.success && parsed.data.activity) {
|
|
50
|
+
const activityQuotes = parsed.data.activity.evidence.map(site => ({ repo: parsed.data.siteRepo ?? context.repoKey, path: site.path, text: site.excerpt }));
|
|
51
|
+
// Optional activity cannot weaken the original-byte evidence boundary or cost a substantive finding.
|
|
52
|
+
activityQuoteCount = activityQuotes.length;
|
|
53
|
+
quotes += activityQuoteCount;
|
|
54
|
+
const invalid = activityQuotes.filter(invalidQuote).length;
|
|
55
|
+
invalidQuotes += invalid;
|
|
56
|
+
if (invalid)
|
|
57
|
+
delete parsed.data.activity;
|
|
58
|
+
if (parsed.data.activity && (!resultCheckFinding.safeParse(parsed.data).success || !intakeFinding.safeParse(parsed.data).success)) {
|
|
59
|
+
const substantive = { ...parsed.data };
|
|
60
|
+
delete substantive.activity;
|
|
61
|
+
if (resultCheckFinding.safeParse(substantive).success && intakeFinding.safeParse(substantive).success)
|
|
62
|
+
delete parsed.data.activity;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (parsed.success && !parsed.data.activity)
|
|
66
|
+
droppedQuotes += activityQuoteCount;
|
|
41
67
|
const evidence = parsed.success ? evidenceQuotes(parsed.data, context.repoKey) : [];
|
|
42
68
|
quotes += evidence.length;
|
|
43
|
-
const invalid = evidence.filter(
|
|
44
|
-
const source = quote.repo === context.repoKey && typeof quote.path === "string" && Object.hasOwn(context.files, quote.path)
|
|
45
|
-
? original(quote.path) : null;
|
|
46
|
-
const invalid = !quote.text.length || source === null || !source.includes(quote.text);
|
|
47
|
-
if (invalid)
|
|
48
|
-
onInvalidQuote?.({ text: quote.text, source });
|
|
49
|
-
return invalid;
|
|
50
|
-
}).length;
|
|
69
|
+
const invalid = evidence.filter(invalidQuote).length;
|
|
51
70
|
invalidQuotes += invalid;
|
|
52
|
-
const schemaValid = parsed.success && resultCheckFinding.safeParse(parsed.data).success &&
|
|
71
|
+
const schemaValid = parsed.success && resultCheckFinding.safeParse(parsed.data).success && intakeFinding.safeParse(parsed.data).success;
|
|
53
72
|
if (!schemaValid)
|
|
54
73
|
schemaDroppedFindings++;
|
|
55
74
|
if (index >= RESULT_CHECK_LIMITS.findings)
|
|
56
75
|
capDroppedFindings++;
|
|
57
76
|
if (!schemaValid || !parsed.success || invalid || index >= RESULT_CHECK_LIMITS.findings) {
|
|
58
77
|
// Removing one supporting quote could change the claim or invalidate source-note indices.
|
|
59
|
-
droppedQuotes += evidence.length;
|
|
78
|
+
droppedQuotes += evidence.length + (parsed.success && parsed.data.activity ? activityQuoteCount : 0);
|
|
60
79
|
}
|
|
61
80
|
else {
|
|
62
81
|
accepted.push(parsed.data);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { readFindingSettings } from "./finding-settings.js";
|
|
2
|
+
import { formatSavedFindings } from "./finding-display.js";
|
|
3
|
+
import { skippedResultText } from "./skipped-files.js";
|
|
4
|
+
/** Terminal and grouped follow-ups use the same final result presentation. */
|
|
5
|
+
export function formatSavedResults(result, settings, numbersEnabled, offer = true, options = {}) {
|
|
6
|
+
return formatSavedFindings(result, { numbersEnabled, audienceEnabled: result.audienceEnabled, notActiveEnabled: result.notActiveEnabled,
|
|
7
|
+
agentReadableResults: options.agentReadableResults ?? readFindingSettings(settings).agentReadableResults, projectId: result.projectId, surface: options.surface })
|
|
8
|
+
+ skippedResultText(result.skippedFiles ?? [], settings, { offer, surface: options.surface, folder: options.analyzedFolder }) + `Full results: ${result.url}\n`
|
|
9
|
+
+ (result.architectureUrl ? `Architecture: ${result.architectureUrl}\n` : "");
|
|
10
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readdir, readFile, unlink } from "node:fs/promises";
|
|
2
2
|
import { dirname, join, resolve } from "node:path";
|
|
3
3
|
import { outputPaths, writeOutputFiles } from "../adapters/output-paths.js";
|
|
4
|
-
import { analysisScopeSchema, chooseScopeResultSchema,
|
|
4
|
+
import { analysisScopeSchema, chooseScopeResultSchema, INCLUDED_FILES_CAPTION, OMITTED_IMPORTS_CAPTION, OMITTED_FILES_CAPTION, SCOPE_CHOICE_INSTRUCTION, INCOMPLETE_IMPORTS_MESSAGE, IMPORT_DISCOVERY_LIMIT_MESSAGE, INVALID_SCOPE_MESSAGE, SCOPE_RETRY_TEMPLATE } from "../contract/scope-choice.js";
|
|
5
5
|
import { scopeChoiceRefusal } from "../contract/selection-offer.js";
|
|
6
6
|
import { scopeChoicesEnabled } from "../adapters/settings.js";
|
|
7
7
|
export async function loadAnalysisScope(options, cwd, settings) {
|
|
@@ -24,7 +24,7 @@ export function scopeChoiceText(value, retryPaths = {}) {
|
|
|
24
24
|
const refusal = scopeChoiceRefusal(result);
|
|
25
25
|
if (refusal)
|
|
26
26
|
return refusal + "\n";
|
|
27
|
-
const lines = [
|
|
27
|
+
const lines = [result.text];
|
|
28
28
|
if (!result.coverageComplete && result.refusedEdges.length > 0)
|
|
29
29
|
lines.push(INCOMPLETE_IMPORTS_MESSAGE);
|
|
30
30
|
if (!result.coverageComplete && result.frontierCount > 0)
|
|
@@ -32,7 +32,7 @@ export function scopeChoiceText(value, retryPaths = {}) {
|
|
|
32
32
|
result.choices.forEach((choice, index) => {
|
|
33
33
|
lines.push(`${index + 1}. ${choice.label}`, choice.description, INCLUDED_FILES_CAPTION, ...choice.includes.map(path => ` ${path}`));
|
|
34
34
|
if (choice.omitted.length > 0)
|
|
35
|
-
lines.push(OMITTED_IMPORTS_CAPTION, ...choice.omitted.map(path => ` ${path}`));
|
|
35
|
+
lines.push(choice.scope.entries ? OMITTED_FILES_CAPTION : OMITTED_IMPORTS_CAPTION, ...choice.omitted.map(path => ` ${path}`));
|
|
36
36
|
if (choice.fits !== false && retryPaths[index]) {
|
|
37
37
|
const entry = choice.scope.entry.startsWith("-") ? `./${choice.scope.entry}` : choice.scope.entry;
|
|
38
38
|
lines.push(SCOPE_RETRY_TEMPLATE.replace("{quoted-entry}", () => shellQuote(entry))
|
package/dist/cli/sign-in.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ApiError } from "./api.js";
|
|
1
|
+
import { ApiError, SavedSignInError } from "./api.js";
|
|
2
2
|
import { readCredentials, saveCredentials } from "./credentials.js";
|
|
3
3
|
import { emailSignInMessages } from "../contract/email-sign-in-messages.js";
|
|
4
4
|
import { cliApiMessages } from "../contract/cli-api-messages.js";
|
|
@@ -89,7 +89,7 @@ export async function signInStatus(ctx) {
|
|
|
89
89
|
return { signedIn: true };
|
|
90
90
|
}
|
|
91
91
|
catch (error) {
|
|
92
|
-
if (error instanceof ApiError && error.status === 401)
|
|
92
|
+
if (error instanceof SavedSignInError || error instanceof ApiError && error.status === 401)
|
|
93
93
|
return { signedIn: false };
|
|
94
94
|
throw error;
|
|
95
95
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { skippedFilesEnabled } from "../analysis-core/settings.js";
|
|
2
|
+
import { skippedFilesNotice, ANALYZE_SKIPPED_FILES_OFFER } from "../analysis-core/analysis/code-agents/skipped-files.js";
|
|
3
|
+
import { analyzeCommand } from "../contract/agent-choice.js";
|
|
4
|
+
function terminalOffer(folder) {
|
|
5
|
+
return `To analyze them separately, run ${analyzeCommand(folder)} --skipped.`;
|
|
6
|
+
}
|
|
7
|
+
export function skippedResultText(files, settings = {}, options = {}) {
|
|
8
|
+
if (!skippedFilesEnabled(settings))
|
|
9
|
+
return "";
|
|
10
|
+
const notice = skippedFilesNotice(files);
|
|
11
|
+
if (!notice)
|
|
12
|
+
return "";
|
|
13
|
+
const offer = options.offer === false ? "" : `${options.surface === "mcp" ? ANALYZE_SKIPPED_FILES_OFFER : terminalOffer(options.folder ?? ".")}\n`;
|
|
14
|
+
return `${notice}\n${offer}`;
|
|
15
|
+
}
|
|
16
|
+
export function followupPlanText(plan) {
|
|
17
|
+
const count = plan.groups.length;
|
|
18
|
+
return [count ? `This will run ${count} ${count === 1 ? "analysis" : "analyses"}.` : "No skipped files remain to analyze.",
|
|
19
|
+
...plan.tooLarge.map(path => `${path}: too large to analyze.`),
|
|
20
|
+
...plan.unavailable.map(path => `${path}: could not be read.`),
|
|
21
|
+
...(plan.missingContext ?? []).map(file => `${file.path}: some imported files could not be included: ${file.imports.join(", ")}.`), ""].join("\n");
|
|
22
|
+
}
|
package/dist/cli/summary.js
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
|
+
import { skippedResultText } from "./skipped-files.js";
|
|
1
2
|
import { findingPresentation } from "../analysis-core/findings/presentation.js";
|
|
2
|
-
import { findingDisplayRows, TEAM_WORKFLOW_HEADING } from "./finding-display.js";
|
|
3
|
+
import { activityAdvice, findingDisplayRows, notActiveCountLine, TEAM_WORKFLOW_HEADING } from "./finding-display.js";
|
|
4
|
+
import { groupByActivity } from "../analysis-core/analysis/schemas.js";
|
|
3
5
|
export function formatSummary(report, options) {
|
|
4
6
|
const lines = ["# Counterfact analysis", "",
|
|
5
7
|
`${report.findings.length} findings; ${report.structures.length} structural proposals.`, ""];
|
|
8
|
+
const skipped = skippedResultText(report.refusedFiles ?? [], { CF_SKIPPED_FILES: options.skippedFilesEnabled === false ? "0" : "1" }, { offer: options.skippedOffer, folder: options.analyzedFolder });
|
|
9
|
+
if (skipped)
|
|
10
|
+
lines.push(skipped.trimEnd(), "");
|
|
6
11
|
if (!report.findings.length)
|
|
7
12
|
lines.push("No findings.", "");
|
|
8
13
|
const findings = report.findings.map(finding => {
|
|
@@ -25,6 +30,15 @@ export function formatSummary(report, options) {
|
|
|
25
30
|
if (finding.fixes.length)
|
|
26
31
|
lines.push(...finding.fixes.map(fix => `- ${fix.title}`), "");
|
|
27
32
|
}
|
|
33
|
+
const { notActive } = groupByActivity(findings, options.notActiveEnabled === true);
|
|
34
|
+
if (notActive.length) {
|
|
35
|
+
lines.push("<details>", `<summary>${notActiveCountLine(notActive.length)}</summary>`, "");
|
|
36
|
+
for (const finding of notActive) {
|
|
37
|
+
const activity = finding.activity;
|
|
38
|
+
lines.push(`### ${finding.title}`, "", `${activity.state === "dead" ? "Dead" : "Switched off"}: ${activity.reason}`, "", activityAdvice(activity), "", finding.cause, "", ...finding.fixes.map(fix => `- ${fix.title}: ${fix.approach}`), "");
|
|
39
|
+
}
|
|
40
|
+
lines.push("</details>", "");
|
|
41
|
+
}
|
|
28
42
|
if (report.structures.length)
|
|
29
43
|
lines.push("## Structural proposals", "", ...report.structures.flatMap(proposal => {
|
|
30
44
|
const presentation = findingPresentation({ ...proposal, title: proposal.body.title });
|
|
@@ -1,14 +1,37 @@
|
|
|
1
|
+
import { CLI_VERSION } from "./cli-version.js";
|
|
1
2
|
export const CHOOSE_AGENT_MESSAGE = "Counterfact couldn't tell which folder holds your agent. Run it again on that folder, for example";
|
|
2
3
|
export const NO_AGENT_MESSAGE = "Counterfact couldn't find an agent prompt in this folder. Pass a folder containing an agent prompt or a model call with a system prompt.";
|
|
4
|
+
const ANALYZE_COMMAND = `npx -y @counterfact-labs/cli@${CLI_VERSION} analyze`;
|
|
5
|
+
export function analyzeCommand(folder) {
|
|
6
|
+
return `${ANALYZE_COMMAND} ${shellArgument(folder)}`;
|
|
7
|
+
}
|
|
3
8
|
/** The local importer supplies the choice; transport keeps its data and wording together. */
|
|
4
9
|
export class AgentChoiceError extends Error {
|
|
5
10
|
agentChoice;
|
|
6
11
|
constructor(agentChoice) {
|
|
7
|
-
|
|
8
|
-
super(agentChoice.kind === "choose_agent" ? [`${CHOOSE_AGENT_MESSAGE} counterfact-labs analyze ${shellArgument(folders[0])}.`, "These folders look like agents:", ...folders].join("\n") : NO_AGENT_MESSAGE);
|
|
12
|
+
super(agentChoiceMessage(agentChoice));
|
|
9
13
|
this.agentChoice = agentChoice;
|
|
10
14
|
}
|
|
11
15
|
}
|
|
16
|
+
export function agentChoiceMessage(agentChoice, surface = "terminal") {
|
|
17
|
+
if (agentChoice.kind === "choose_scope")
|
|
18
|
+
return scopeChoiceMessage(agentChoice, surface);
|
|
19
|
+
if (agentChoice.kind === "choose_agent")
|
|
20
|
+
return folderChoiceMessage(agentChoice);
|
|
21
|
+
return NO_AGENT_MESSAGE;
|
|
22
|
+
}
|
|
23
|
+
function scopeChoiceMessage(agentChoice, surface) {
|
|
24
|
+
return ["Choose one part to analyze:", ...agentChoice.candidates.map((candidate, index) => {
|
|
25
|
+
const label = `${index + 1}. ${candidate.label}`;
|
|
26
|
+
if (surface === "mcp")
|
|
27
|
+
return label;
|
|
28
|
+
return `${label}\n ${analyzeCommand(candidate.folder)} --scope ${shellArgument(JSON.stringify(candidate.files))}`;
|
|
29
|
+
})].join("\n");
|
|
30
|
+
}
|
|
31
|
+
function folderChoiceMessage(agentChoice) {
|
|
32
|
+
const folders = [...new Set(agentChoice.candidates.map(candidate => candidate.folder))];
|
|
33
|
+
return [`${CHOOSE_AGENT_MESSAGE} ${analyzeCommand(folders[0])}.`, "These folders look like agents:", ...folders].join("\n");
|
|
34
|
+
}
|
|
12
35
|
/** Folder names must remain literal when a user pastes the suggested command. */
|
|
13
36
|
function shellArgument(folder) {
|
|
14
37
|
return /^[a-zA-Z0-9_./-]+$/.test(folder) ? folder : `'${folder.replaceAll("'", "'\\''")}'`;
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import { CLI_VERSION } from "./cli-version.js";
|
|
1
2
|
export const cliApiMessages = Object.freeze({
|
|
2
3
|
unreachable: "Can't reach Counterfact. Try again shortly.",
|
|
3
4
|
invalid_response: "Counterfact returned an invalid response. Try again shortly.",
|
|
4
5
|
request_failed: "The request could not be completed. Try again shortly.",
|
|
6
|
+
unreadable_sign_in: "Your saved sign-in can't be read. Sign in again.",
|
|
7
|
+
unreadable_sign_in_terminal: `Your saved sign-in can't be read. Sign in again with npx -y @counterfact-labs/cli@${CLI_VERSION} login.`,
|
|
5
8
|
unauthorized: "Not signed in. Run counterfact-labs login.",
|
|
6
9
|
invalid_console_url: "COUNTERFACT_URL must be an HTTP or HTTPS address without credentials or query fields.",
|
|
7
10
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { FOLDER_TOO_LARGE_MESSAGE } from "../analysis-core/errors.js";
|
|
2
2
|
import { cliApiMessages } from "./cli-api-messages.js";
|
|
3
3
|
import { emailSignInMessages } from "./email-sign-in-messages.js";
|
|
4
|
+
import { ANALYSIS_TOO_LARGE_MESSAGE, INVALID_SELECTION_MESSAGE } from "./scope-choice.js";
|
|
4
5
|
export const START_FAILURE_MESSAGE = "This couldn't start. Try again.";
|
|
5
6
|
export const RESERVATION_EXPIRED_MESSAGE = "This reservation expired. Start a new analysis.";
|
|
6
7
|
export const FINISH_FAILURE_MESSAGE = "The analysis couldn't finish. Try again.";
|
|
@@ -14,11 +15,13 @@ export const RESULTS_TOO_LARGE_MESSAGE = "The results were too large to save. Tr
|
|
|
14
15
|
export const COMMITTED_FILES_MESSAGE = "Counterfact analyzes the committed version of the agent's files. Commit your changes, then try again.";
|
|
15
16
|
const unsupportedVersion = "This version of Counterfact can't run analyses. Update it: npm install -g @counterfact-labs/cli@latest.";
|
|
16
17
|
// The console separates over-limit costs from version or route mismatches, so each code gets its own recovery guidance.
|
|
17
|
-
const
|
|
18
|
+
const localReadFailure = "Counterfact couldn't read the selected file or folder. Check that it exists and is readable, then try again.";
|
|
18
19
|
export const localRefusals = {
|
|
19
|
-
outside_project:
|
|
20
|
-
launch_folder_unavailable:
|
|
21
|
-
folder_unavailable:
|
|
20
|
+
outside_project: INVALID_SELECTION_MESSAGE,
|
|
21
|
+
launch_folder_unavailable: "Counterfact couldn't read the launch folder. Check that it exists and is readable, then try again.",
|
|
22
|
+
folder_unavailable: localReadFailure,
|
|
23
|
+
local_read_failed: localReadFailure,
|
|
24
|
+
local_git_failed: "Counterfact couldn't read this folder's Git information. Check that Git works in this folder, then try again.",
|
|
22
25
|
repeat_too_soon: FINISH_FAILURE_MESSAGE,
|
|
23
26
|
};
|
|
24
27
|
/** Both console clients use the same public messages for server refusals. */
|
|
@@ -28,7 +31,7 @@ export const cliRefusals = {
|
|
|
28
31
|
local_checks_paused: "Analyses are paused right now. Try again later.",
|
|
29
32
|
cli_update_required: "Update Counterfact: run npm install -g @counterfact-labs/cli@latest.",
|
|
30
33
|
release_unavailable: unsupportedVersion,
|
|
31
|
-
plan_over_limit:
|
|
34
|
+
plan_over_limit: ANALYSIS_TOO_LARGE_MESSAGE,
|
|
32
35
|
invalid_plan: unsupportedVersion,
|
|
33
36
|
invalid_body: "Counterfact couldn't read this request. Update Counterfact, then try again.",
|
|
34
37
|
body_too_large: "This request is too large. The limit is 2 MB.",
|
|
@@ -80,12 +83,35 @@ const reasonValues = [
|
|
|
80
83
|
"folder_busy", "completion_settings_mismatch",
|
|
81
84
|
];
|
|
82
85
|
const ownedReasons = new Set(reasonValues);
|
|
86
|
+
const readErrorCodes = new Set(["ENOENT", "EACCES", "EPERM", "ENOTDIR", "EISDIR", "ELOOP", "EMFILE", "ENFILE", "EIO", "ESTALE", "EBADF", "EINVAL", "ENOMEM", "EAGAIN", "EBUSY"]);
|
|
87
|
+
/** Classify local failures without copying paths, command output or file contents into public errors. */
|
|
88
|
+
export function localFailure(error) {
|
|
89
|
+
if (!(error instanceof Error))
|
|
90
|
+
return undefined;
|
|
91
|
+
if ("reason" in error && error.reason === "local_git_failed")
|
|
92
|
+
return { message: localRefusals.local_git_failed, reason: "local_git_failed" };
|
|
93
|
+
// Only the selection reader knows whether a filesystem operation belongs to the agent.
|
|
94
|
+
if (!("localOperation" in error) || error.localOperation !== "read")
|
|
95
|
+
return undefined;
|
|
96
|
+
const syscall = "syscall" in error ? error.syscall : undefined;
|
|
97
|
+
const code = "code" in error ? error.code : undefined;
|
|
98
|
+
if (typeof code === "string" && readErrorCodes.has(code) && typeof syscall === "string"
|
|
99
|
+
&& (/^(?:read|stat|lstat|scandir|opendir|realpath|readlink|access)$/.test(syscall)
|
|
100
|
+
|| syscall === "open")
|
|
101
|
+
|| "code" in error && error.code === "ERR_ACCESS_DENIED" && "permission" in error && error.permission === "FileSystemRead") {
|
|
102
|
+
return { message: localRefusals.local_read_failed, reason: "local_read_failed" };
|
|
103
|
+
}
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
83
106
|
/** Only fixed reasons may cross the MCP boundary; diagnostics can contain customer data. */
|
|
84
107
|
export function failureReason(error) {
|
|
85
108
|
if (!(error instanceof Error))
|
|
86
109
|
return "request_failed";
|
|
87
110
|
if (error instanceof CliRefusalError)
|
|
88
111
|
return error.code;
|
|
112
|
+
const local = localFailure(error);
|
|
113
|
+
if (local)
|
|
114
|
+
return local.reason;
|
|
89
115
|
if ("reason" in error && typeof error.reason === "string" && ownedReasons.has(error.reason))
|
|
90
116
|
return error.reason;
|
|
91
117
|
const code = "code" in error ? error.code : undefined;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const CLI_VERSION = "0.2.
|
|
1
|
+
export const CLI_VERSION = "0.2.10";
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
export function savedAnalysisUrl(projectId, consoleOrigin) {
|
|
2
2
|
return new URL(`/projects/${projectId}/findings`, consoleOrigin).href;
|
|
3
3
|
}
|
|
4
|
+
export function savedArchitectureUrl(projectId, consoleOrigin) {
|
|
5
|
+
return new URL(`/projects/${projectId}/architecture/overview`, consoleOrigin).href;
|
|
6
|
+
}
|
|
4
7
|
import { isLoopbackHost } from "../gatekeeper/loopback.js";
|
|
5
8
|
/** Bearer credentials may use plain HTTP only on this machine. */
|
|
6
9
|
export function consoleSchemeAllowed(url) {
|
package/dist/contract/console.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import {
|
|
3
|
-
import { inspectFixSchema, otherSiteSchema, rawFindingSchema } from "../analysis-core/analysis/schemas.js";
|
|
4
|
-
import { sourceNotesSchema } from "../analysis-core/analysis/source-notes/types.js";
|
|
5
|
-
import { parseAnalysisSize } from "../analysis-core/conventions/analysis-fit.js";
|
|
2
|
+
import { parseAnalysisSize } from "../analysis-core/contracts/analysis-size.js";
|
|
6
3
|
import { canonicalUuidSchema as uuid, matchingFindingNumbers, numberedFindingsSchema } from "./findings.js";
|
|
7
|
-
import {
|
|
4
|
+
import { publicReportSchema } from "../analysis-core/contracts/relay-analysis.js";
|
|
5
|
+
import { PLANNED_INPUT_BYTES } from "../analysis-core/requests/planned-limits.js";
|
|
6
|
+
import { projectArchitectureSchema } from "./project-architecture.js";
|
|
7
|
+
import { intakeFinding as localFindingSchema, nonblankText as text, RESULT_CHECK_LIMITS, validIntakeText } from "../analysis-core/contracts/intake-finding.js";
|
|
8
|
+
export { localFindingSchema };
|
|
8
9
|
import { selectionStartIdentitySchema } from "./selection-metadata.js";
|
|
9
10
|
export const id = z.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._:-]{0,127}$/);
|
|
10
11
|
const money = z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER);
|
|
@@ -16,12 +17,14 @@ export const ANALYSIS_SIZE_HEADER = "x-counterfact-analysis-size";
|
|
|
16
17
|
export const START_ATTEMPT_HEADER = "x-counterfact-start-attempt";
|
|
17
18
|
export const SELECTION_IDENTITY_HEADER = "x-counterfact-selection";
|
|
18
19
|
export const ANALYSIS_SIZE_HEADER_LIMIT = 256;
|
|
20
|
+
export const ADD_TO_ANALYSIS_HEADER = "x-counterfact-add-to-analysis";
|
|
19
21
|
/** Additive headers let older consoles keep accepting the unchanged, strict start body. */
|
|
20
22
|
export function serializeLocalStartMetadata(metadata) {
|
|
21
23
|
const size = JSON.stringify(parseAnalysisSize(metadata.analysisSize));
|
|
22
24
|
if (Buffer.byteLength(size, "utf8") > ANALYSIS_SIZE_HEADER_LIMIT)
|
|
23
25
|
throw new Error("Invalid analysis size header");
|
|
24
26
|
return { [ANALYSIS_SIZE_HEADER]: size, [START_ATTEMPT_HEADER]: uuid.parse(metadata.startAttempt),
|
|
27
|
+
...(metadata.addToAnalysisId ? { [ADD_TO_ANALYSIS_HEADER]: uuid.parse(metadata.addToAnalysisId) } : {}),
|
|
25
28
|
...(metadata.selection === undefined ? {} : { [SELECTION_IDENTITY_HEADER]: JSON.stringify(selectionStartIdentitySchema.parse(metadata.selection)) }) };
|
|
26
29
|
}
|
|
27
30
|
/** Missing measurements preserve old clients; present measurements must be complete. */
|
|
@@ -33,6 +36,7 @@ export function parseLocalStartMetadata(headers) {
|
|
|
33
36
|
throw new Error("Invalid analysis size header");
|
|
34
37
|
const selection = headers.get(SELECTION_IDENTITY_HEADER);
|
|
35
38
|
return { analysisSize: parseAnalysisSize(JSON.parse(raw)), startAttempt: uuid.parse(headers.get(START_ATTEMPT_HEADER)),
|
|
39
|
+
...(headers.get(ADD_TO_ANALYSIS_HEADER) === null ? {} : { addToAnalysisId: uuid.parse(headers.get(ADD_TO_ANALYSIS_HEADER)) }),
|
|
36
40
|
...(selection === null ? {} : { selection: selectionStartIdentitySchema.parse(JSON.parse(z.string().max(256).parse(selection))) }) };
|
|
37
41
|
}
|
|
38
42
|
const localStartPath = z.string().min(1).max(4096).refine(value => !value.includes("\\") && !value.includes("\0") && !value.startsWith("/")
|
|
@@ -47,7 +51,7 @@ export const localStartRequestSchema = z.strictObject({
|
|
|
47
51
|
export const localStartResponseSchema = z.strictObject({ jobId: uuid, projectId: uuid });
|
|
48
52
|
export const relayPlanSchema = z.strictObject({
|
|
49
53
|
bundleVersion: id, bundleHash: z.string().regex(/^[a-f0-9]{64}$/), relayVersion: id,
|
|
50
|
-
calls: z.array(z.strictObject({ routeId: id, model: id, calls: count(512), maxInputBytes: count(
|
|
54
|
+
calls: z.array(z.strictObject({ routeId: id, model: id, calls: count(512), maxInputBytes: count(PLANNED_INPUT_BYTES),
|
|
51
55
|
maxInputTokens: count(200_000), maxOutputTokens: count(64_000) })).min(1).max(64)
|
|
52
56
|
.refine(calls => new Set(calls.map(call => call.routeId)).size === calls.length && calls.reduce((sum, call) => sum + call.calls, 0) <= 512),
|
|
53
57
|
});
|
|
@@ -89,44 +93,19 @@ export const consoleIntakeResultSchema = z.strictObject({
|
|
|
89
93
|
export const LOCAL_FINDINGS_BODY_LIMIT = 2 * 1024 * 1024;
|
|
90
94
|
export const LOCAL_FINDINGS_QUOTE_LIMIT = RESULT_CHECK_LIMITS.quoteBytes;
|
|
91
95
|
export const LOCAL_FINDINGS_TEXT_LIMIT = RESULT_CHECK_LIMITS.textBytes;
|
|
92
|
-
function shortFindingFields(value) {
|
|
93
|
-
if (typeof value === "string")
|
|
94
|
-
return Buffer.byteLength(value, "utf8") <= LOCAL_FINDINGS_TEXT_LIMIT;
|
|
95
|
-
return value === null || typeof value !== "object" || Object.values(value).every(shortFindingFields);
|
|
96
|
-
}
|
|
97
|
-
const quote = z.string().refine(value => Buffer.byteLength(value, "utf8") <= LOCAL_FINDINGS_QUOTE_LIMIT);
|
|
98
|
-
const text = z.string().min(1).refine(value => value.trim().length > 0);
|
|
99
|
-
const delta = fixDeltaSchema.extend({
|
|
100
|
-
nodes: z.array(fixDeltaSchema.shape.nodes.element.strict()),
|
|
101
|
-
edges: z.array(fixDeltaSchema.shape.edges.element.strict()),
|
|
102
|
-
}).strict();
|
|
103
|
-
const fix = inspectFixSchema.extend({
|
|
104
|
-
...fixFactsSchema.shape,
|
|
105
|
-
targetFiles: z.array(inspectFixSchema.shape.targetFiles.element.strict()),
|
|
106
|
-
delta: delta.nullable(),
|
|
107
|
-
}).strict();
|
|
108
|
-
const notes = sourceNotesSchema.extend({
|
|
109
|
-
sites: z.array(sourceNotesSchema.shape.sites.element.extend({ keyPhrase: quote }).strict()),
|
|
110
|
-
counterEvidence: z.array(sourceNotesSchema.shape.counterEvidence.element.extend({ excerpt: quote }).strict()),
|
|
111
|
-
}).strict();
|
|
112
|
-
export const localFindingSchema = rawFindingSchema.extend({
|
|
113
|
-
audience: findingAudienceSchema.optional(),
|
|
114
|
-
summary: z.string().nullable(),
|
|
115
|
-
category: rawFindingSchema.shape.category.removeCatch(),
|
|
116
|
-
question: rawFindingSchema.shape.question.removeCatch().nullable(),
|
|
117
|
-
siteRepo: text.nullable(),
|
|
118
|
-
site: rawFindingSchema.shape.site.unwrap().strict().nullable(),
|
|
119
|
-
excerpt: quote.nullable(),
|
|
120
|
-
otherSites: z.array(otherSiteSchema.extend({ repo: text, excerpt: otherSiteSchema.shape.excerpt.pipe(quote) }).strict()),
|
|
121
|
-
fixes: z.array(fix).min(1),
|
|
122
|
-
sourceNotes: notes.nullable().optional(),
|
|
123
|
-
}).strict().refine(shortFindingFields);
|
|
124
96
|
const payload = z.strictObject({
|
|
125
97
|
checkId: z.uuid(), projectId: z.uuid(), reservationId: uuid.optional(),
|
|
98
|
+
addToAnalysisId: uuid.optional(),
|
|
99
|
+
coverage: z.strictObject({ skippedFiles: z.array(z.strictObject({ path: localStartPath,
|
|
100
|
+
reason: publicReportSchema.shape.refusedFiles.element.shape.reason })),
|
|
101
|
+
readPaths: z.array(localStartPath) }).optional(),
|
|
102
|
+
findingAssessments: z.array(z.strictObject({ fingerprint: z.string().max(8192), support: z.enum(["complete", "incomplete"]),
|
|
103
|
+
confidence: z.enum(["sure", "not sure"]), weak: z.boolean(), reason: z.string().max(4096) })).optional(),
|
|
126
104
|
agent: z.strictObject({
|
|
127
105
|
repository: text.max(4096), commit: z.string().regex(/^(?:[a-f\d]{40}|[a-f\d]{64})$/i).nullable(),
|
|
128
106
|
files: z.array(z.strictObject({ path: text.max(4096), hash: z.string().regex(/^[a-f\d]{64}$/i) })).min(1),
|
|
129
107
|
}),
|
|
108
|
+
architecture: projectArchitectureSchema.optional(),
|
|
130
109
|
findings: z.array(localFindingSchema), bundleVersion: text.max(128), relayVersion: text.max(128), counterfactLocalVersion: text.max(128),
|
|
131
110
|
}).refine(validIntakeText);
|
|
132
111
|
// Original JSON size is checked before a shared schema can trim text or apply defaults.
|
package/dist/contract/engine.js
CHANGED
|
@@ -3,7 +3,7 @@ import { ANALYSIS_PROTOCOL, analysisCapabilities } from "../analysis-core/contra
|
|
|
3
3
|
import { stableStringify } from "../analysis-core/llm/stable-json.js";
|
|
4
4
|
import { workloadHardware } from "../gatekeeper/trust/schema.js";
|
|
5
5
|
import { kmsKeyName } from "../transport/kms.js";
|
|
6
|
-
export const ENGINE_ABI =
|
|
6
|
+
export const ENGINE_ABI = 2;
|
|
7
7
|
export const ENGINE_SPAWNER_CONTRACT = 1;
|
|
8
8
|
const hash = z.string().regex(/^[0-9a-f]{64}$/);
|
|
9
9
|
const version = z.string().min(1).max(128).regex(/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { analysisRelease } from "./engine.js";
|
|
3
|
-
import { executionRequest } from "../analysis-core/contracts/
|
|
3
|
+
import { executionRequest } from "../analysis-core/contracts/execution-request.js";
|
|
4
|
+
import { PLANNED_INPUT_BYTES } from "../analysis-core/requests/planned-limits.js";
|
|
4
5
|
import { compactJwtPattern, hexChallenge } from "./attestation.js";
|
|
5
6
|
export { PRODUCTION_EVIDENCE_VERSION, RELAY_CHANNEL_AUDIENCE, RELAY_PORT, RELAY_URL, RELAY_HEALTH_PATH, RELAY_HEALTH_URL, GOOGLE_ATTESTATION_ISSUER, MAX_ATTESTATION_BYTES, attestationChallenge, productionEvidence, evidenceBinding, googleAttestationClaims, observedAttestationClaims, probeAttestationClaims, attestationClaims } from "./attestation.js";
|
|
6
7
|
/**
|
|
@@ -66,14 +67,14 @@ const outputCount = z.number().int().nonnegative().max(64_000);
|
|
|
66
67
|
/** A reservation lookup establishes the owner without reserving spend or contacting the model provider. */
|
|
67
68
|
export const reservationCheck = z.object({ action: z.literal("check"), ...billingScope }).strict();
|
|
68
69
|
export const reservationIdentity = z.object({ clientId: z.string().min(1).max(128) }).strict();
|
|
69
|
-
export const callCounts = z.object({ inputBytes: z.number().int().positive().max(
|
|
70
|
+
export const callCounts = z.object({ inputBytes: z.number().int().positive().max(PLANNED_INPUT_BYTES),
|
|
70
71
|
inputTokens: inputCount.positive(), maxOutputTokens: outputCount.positive() }).strict();
|
|
71
72
|
export const authorizationRequest = z.object({ action: z.literal("authorize"), ...billingScope,
|
|
72
73
|
routeId: billingId, templateId: billingId, answerFormatId: billingId, model: billingId, ...callCounts.shape }).strict();
|
|
73
74
|
export const callAuthorization = z.object({ attemptId: z.uuid(), model: billingId, role: z.enum(["required", "optional"]),
|
|
74
75
|
/** Stable opaque owner supplied only by the authenticated authorization service. */
|
|
75
76
|
clientId: reservationIdentity.shape.clientId,
|
|
76
|
-
deadline: z.iso.datetime(), countLimits: z.object({ maxInputBytes: z.number().int().positive().max(
|
|
77
|
+
deadline: z.iso.datetime(), countLimits: z.object({ maxInputBytes: z.number().int().positive().max(PLANNED_INPUT_BYTES),
|
|
77
78
|
maxInputTokens: inputCount.positive(), maxOutputTokens: outputCount.positive() }).strict() }).strict();
|
|
78
79
|
export const providerUsage = z.object({ kind: z.enum(["actual", "estimated", "unpaid"]),
|
|
79
80
|
outcome: z.enum(["success", "unknown", "retryable_failure", "terminal_provider_failure", "schema_refusal", "leak_refusal"]),
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { PROJECT_ARCHITECTURE_BYTES, projectArchitectureSchema, projectArchitecture } from "../analysis-core/analysis/project-architecture.js";
|