@dailephd/my-dev-kit-lab 0.8.2 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/README.md +18 -3
- package/benchmarks/contracts/agent-success-rate-project-profiles.json +33 -0
- package/benchmarks/contracts/agent-success-rate-tasks.json +671 -0
- package/benchmarks/projects/agent-success-inventory-node/.gitattributes +2 -0
- package/benchmarks/projects/agent-success-inventory-node/README.md +74 -0
- package/benchmarks/projects/agent-success-inventory-node/package.json +14 -0
- package/benchmarks/projects/agent-success-inventory-node/src/fulfillmentReport.js +38 -0
- package/benchmarks/projects/agent-success-inventory-node/src/index.js +26 -0
- package/benchmarks/projects/agent-success-inventory-node/src/inventoryStore.js +114 -0
- package/benchmarks/projects/agent-success-inventory-node/src/quantity.js +19 -0
- package/benchmarks/projects/agent-success-inventory-node/src/reservationService.js +45 -0
- package/benchmarks/projects/agent-success-inventory-node/tests/quantity-edge.check.mjs +51 -0
- package/benchmarks/projects/agent-success-inventory-node/tests/quantity-primary.check.mjs +46 -0
- package/benchmarks/projects/agent-success-inventory-node/tests/regression.check.mjs +115 -0
- package/benchmarks/projects/agent-success-inventory-node/tests/report-edge.check.mjs +109 -0
- package/benchmarks/projects/agent-success-inventory-node/tests/report-primary.check.mjs +47 -0
- package/benchmarks/projects/agent-success-inventory-node/tests/reservation-edge.check.mjs +63 -0
- package/benchmarks/projects/agent-success-inventory-node/tests/reservation-primary.check.mjs +44 -0
- package/benchmarks/projects/agent-success-task-board-node/.gitattributes +2 -0
- package/benchmarks/projects/agent-success-task-board-node/README.md +77 -0
- package/benchmarks/projects/agent-success-task-board-node/package.json +14 -0
- package/benchmarks/projects/agent-success-task-board-node/src/index.js +24 -0
- package/benchmarks/projects/agent-success-task-board-node/src/projectSummary.js +15 -0
- package/benchmarks/projects/agent-success-task-board-node/src/taskService.js +58 -0
- package/benchmarks/projects/agent-success-task-board-node/src/taskStore.js +58 -0
- package/benchmarks/projects/agent-success-task-board-node/src/validation.js +31 -0
- package/benchmarks/projects/agent-success-task-board-node/tests/import-edge.check.mjs +65 -0
- package/benchmarks/projects/agent-success-task-board-node/tests/import-primary.check.mjs +31 -0
- package/benchmarks/projects/agent-success-task-board-node/tests/regression.check.mjs +107 -0
- package/benchmarks/projects/agent-success-task-board-node/tests/summary-edge.check.mjs +67 -0
- package/benchmarks/projects/agent-success-task-board-node/tests/summary-primary.check.mjs +54 -0
- package/benchmarks/projects/agent-success-task-board-node/tests/title-edge.check.mjs +39 -0
- package/benchmarks/projects/agent-success-task-board-node/tests/title-primary.check.mjs +29 -0
- package/dist/src/cli/help.js +38 -1
- package/dist/src/commands/runExperimentDescribeCommand.js +52 -1
- package/dist/src/commands/runExperimentRunCommand.js +118 -2
- package/dist/src/core/processTree.js +55 -0
- package/dist/src/core/runMeasuredCommand.js +104 -22
- package/dist/src/evaluation/agentSuccess/applyPatchToSandbox.js +37 -0
- package/dist/src/evaluation/agentSuccess/assessBaseline.js +23 -0
- package/dist/src/evaluation/agentSuccess/corpusTypes.js +74 -0
- package/dist/src/evaluation/agentSuccess/extractPatchCandidate.js +46 -0
- package/dist/src/evaluation/agentSuccess/index.js +14 -0
- package/dist/src/evaluation/agentSuccess/parseUnifiedDiff.js +210 -0
- package/dist/src/evaluation/agentSuccess/patchTypes.js +5 -0
- package/dist/src/evaluation/agentSuccess/readAgentSuccessCorpus.js +183 -0
- package/dist/src/evaluation/agentSuccess/runVerificationCheck.js +100 -0
- package/dist/src/evaluation/agentSuccess/taskPaths.js +34 -0
- package/dist/src/evaluation/agentSuccess/taskTypes.js +14 -0
- package/dist/src/evaluation/agentSuccess/validateAgentSuccessCorpus.js +307 -0
- package/dist/src/evaluation/agentSuccess/validateAgentSuccessTask.js +297 -0
- package/dist/src/evaluation/agentSuccess/validatePatchPolicy.js +66 -0
- package/dist/src/evaluation/agentSuccess/verificationTypes.js +1 -0
- package/dist/src/evaluation/benchmarkSandbox/createBenchmarkSandbox.js +127 -0
- package/dist/src/evaluation/benchmarkSandbox/errors.js +11 -0
- package/dist/src/evaluation/benchmarkSandbox/gitExecutor.js +60 -0
- package/dist/src/evaluation/benchmarkSandbox/index.js +8 -0
- package/dist/src/evaluation/benchmarkSandbox/minimalHostEnv.js +37 -0
- package/dist/src/evaluation/benchmarkSandbox/pathPolicy.js +49 -0
- package/dist/src/evaluation/benchmarkSandbox/removeBenchmarkSandbox.js +74 -0
- package/dist/src/evaluation/benchmarkSandbox/treeSnapshot.js +57 -0
- package/dist/src/evaluation/benchmarkSandbox/types.js +3 -0
- package/dist/src/evaluation/changeSet/captureChangeSet.js +98 -0
- package/dist/src/evaluation/changeSet/index.js +2 -0
- package/dist/src/evaluation/changeSet/types.js +9 -0
- package/dist/src/experiments/defaultRegistry.js +2 -0
- package/dist/src/experiments/plugins/agentSuccessRate/agentTaskProjection.js +12 -0
- package/dist/src/experiments/plugins/agentSuccessRate/analysis.js +425 -0
- package/dist/src/experiments/plugins/agentSuccessRate/analysisArtifact.js +155 -0
- package/dist/src/experiments/plugins/agentSuccessRate/analysisTypes.js +65 -0
- package/dist/src/experiments/plugins/agentSuccessRate/attemptEvidence.js +7 -0
- package/dist/src/experiments/plugins/agentSuccessRate/config.js +123 -0
- package/dist/src/experiments/plugins/agentSuccessRate/contextGeneration.js +497 -0
- package/dist/src/experiments/plugins/agentSuccessRate/execution.js +441 -0
- package/dist/src/experiments/plugins/agentSuccessRate/executionArtifact.js +126 -0
- package/dist/src/experiments/plugins/agentSuccessRate/executionTypes.js +2 -0
- package/dist/src/experiments/plugins/agentSuccessRate/index.js +20 -0
- package/dist/src/experiments/plugins/agentSuccessRate/metadata.js +28 -0
- package/dist/src/experiments/plugins/agentSuccessRate/metrics.js +76 -0
- package/dist/src/experiments/plugins/agentSuccessRate/plugin.js +507 -0
- package/dist/src/experiments/plugins/agentSuccessRate/realAgentExecution.js +250 -0
- package/dist/src/experiments/plugins/agentSuccessRate/realAgentPrompt.js +43 -0
- package/dist/src/experiments/plugins/agentSuccessRate/repairAnalysis.js +148 -0
- package/dist/src/experiments/plugins/agentSuccessRate/repairExecution.js +103 -0
- package/dist/src/experiments/plugins/agentSuccessRate/repairFeedback.js +91 -0
- package/dist/src/experiments/plugins/agentSuccessRate/repairPolicy.js +88 -0
- package/dist/src/experiments/plugins/agentSuccessRate/types.js +1 -0
- package/dist/src/experiments/plugins/warmIndexReuse/plugin.js +2 -3
- package/dist/src/report/experiments/agentSuccessRateReportModel.js +6 -0
- package/dist/src/report/experiments/buildAgentSuccessRateReport.js +255 -0
- package/dist/src/report/experiments/buildPluginExperimentReport.js +27 -2
- package/dist/src/report/experiments/index.js +4 -0
- package/dist/src/report/experiments/renderAgentSuccessRateHtml.js +169 -0
- package/dist/src/report/experiments/renderAgentSuccessRateText.js +139 -0
- package/dist/src/report/experiments/renderPluginExperimentReportHtml.js +3 -0
- package/dist/src/report/experiments/renderPluginExperimentReportText.js +5 -0
- package/docs/METRICS.md +70 -0
- package/package.json +1 -1
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every complete attempt evidence record of one treatment, in execution order. Real-agent treatments list their attempts;
|
|
3
|
+
* a deterministic-fixture treatment (or a Batch 4 style record) is its own single attempt.
|
|
4
|
+
*/
|
|
5
|
+
export function attemptEvidenceOf(treatment) {
|
|
6
|
+
return treatment.attempts && treatment.attempts.length > 0 ? treatment.attempts.map((attempt) => attempt.evidence) : [treatment];
|
|
7
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { parseCommandString } from "../../../core/commandLine.js";
|
|
2
|
+
import { invalidExperimentConfig, isPlainObject, mergeConfig, validExperimentConfig } from "../../config.js";
|
|
3
|
+
import { DEFAULT_AGENT_SUCCESS_KIT_COMMAND } from "./contextGeneration.js";
|
|
4
|
+
import { AGENT_SUCCESS_RATE_MAX_AGENT_TIMEOUT_MS } from "./realAgentExecution.js";
|
|
5
|
+
export const AGENT_SUCCESS_RATE_REAL_AGENT_IDS = ["codex", "claude"];
|
|
6
|
+
export const AGENT_SUCCESS_RATE_REPAIR_ATTEMPT_VALUES = [0, 1, 2];
|
|
7
|
+
export const defaultAgentSuccessRateConfig = {
|
|
8
|
+
outDir: "lab-output/agent-success-rate"
|
|
9
|
+
};
|
|
10
|
+
const SUPPORTED_FIELDS = ["outDir", "caseIds", "benchmarkProjects", "agentId", "includeRealAgents", "timeoutMs", "kitCommand", "repairAttempts"];
|
|
11
|
+
/** Names that are recognized only to give a precise rejection. */
|
|
12
|
+
const REJECTED_FIELD_MESSAGES = {
|
|
13
|
+
commandTemplate: "commandTemplate is not supported; real providers use the fixed adapter invocation.",
|
|
14
|
+
campaignPreset: "campaignPreset is not supported by agent-success-rate.",
|
|
15
|
+
retryUntilSuccess: "retryUntilSuccess is not supported; repair attempts are bounded by repairAttempts (maximum 2).",
|
|
16
|
+
unlimitedRepairs: "unlimitedRepairs is not supported; repair attempts are bounded by repairAttempts (maximum 2).",
|
|
17
|
+
providerMatrix: "providerMatrix is not supported; exactly one provider is selected per run.",
|
|
18
|
+
resumeSession: "resumeSession is not supported.",
|
|
19
|
+
resumeFromCheckpoint: "resumeFromCheckpoint is not supported.",
|
|
20
|
+
automaticProviderSwitching: "automaticProviderSwitching is not supported; a repair always uses the selected provider.",
|
|
21
|
+
strategies: "strategies is not supported; the two treatments are fixed.",
|
|
22
|
+
agentIds: "agentIds is not supported; exactly one provider is selected per run.",
|
|
23
|
+
agents: "agents is not supported; exactly one provider is selected per run."
|
|
24
|
+
};
|
|
25
|
+
export const agentSuccessRateConfigDefinition = {
|
|
26
|
+
fields: [
|
|
27
|
+
{ name: "outDir", type: "string", required: true, description: "Experiment output root." },
|
|
28
|
+
{ name: "caseIds", type: "array", description: "Task IDs to include; input order is preserved." },
|
|
29
|
+
{ name: "benchmarkProjects", type: "array", description: "Benchmark project IDs to include; input order is preserved." },
|
|
30
|
+
{ name: "agentId", type: "string", description: "Real provider for real-agent mode: codex or claude. Absent selects deterministic-fixture mode." },
|
|
31
|
+
{ name: "includeRealAgents", type: "boolean", description: "Must be exactly true whenever agentId is set; rejected otherwise." },
|
|
32
|
+
{ name: "timeoutMs", type: "number", description: "Per-attempt provider timeout in ms (real-agent mode only)." },
|
|
33
|
+
{ name: "kitCommand", type: "string", description: "my-dev-kit command used for context-pack retrieval (real-agent mode only)." },
|
|
34
|
+
{ name: "repairAttempts", type: "number", description: "Repairs allowed after the initial attempt: 0 (default), 1 or 2. Real-agent mode only; at most 3 attempts per treatment." }
|
|
35
|
+
]
|
|
36
|
+
};
|
|
37
|
+
export function validateAgentSuccessRateConfig(config) {
|
|
38
|
+
if (config !== undefined && !isPlainObject(config)) {
|
|
39
|
+
return invalidExperimentConfig(["agent success rate config must be an object."]);
|
|
40
|
+
}
|
|
41
|
+
const errors = [];
|
|
42
|
+
const raw = isPlainObject(config) ? config : {};
|
|
43
|
+
const unsupported = Object.keys(raw).filter((key) => !SUPPORTED_FIELDS.includes(key));
|
|
44
|
+
const explained = unsupported.filter((key) => key in REJECTED_FIELD_MESSAGES);
|
|
45
|
+
const unknown = unsupported.filter((key) => !(key in REJECTED_FIELD_MESSAGES));
|
|
46
|
+
for (const key of explained.sort())
|
|
47
|
+
errors.push(REJECTED_FIELD_MESSAGES[key]);
|
|
48
|
+
if (unknown.length > 0) {
|
|
49
|
+
errors.push(`Unsupported agent-success-rate config field(s): ${unknown.sort().join(", ")}.`);
|
|
50
|
+
}
|
|
51
|
+
const normalized = mergeConfig(defaultAgentSuccessRateConfig, config);
|
|
52
|
+
const outDir = normalized.outDir;
|
|
53
|
+
if (typeof outDir !== "string" || !outDir.trim())
|
|
54
|
+
errors.push("outDir must be a non-empty string.");
|
|
55
|
+
for (const field of ["caseIds", "benchmarkProjects"]) {
|
|
56
|
+
const value = normalized[field];
|
|
57
|
+
if (value === undefined)
|
|
58
|
+
continue;
|
|
59
|
+
if (!Array.isArray(value) || value.length === 0 || !value.every((item) => typeof item === "string" && item.trim())) {
|
|
60
|
+
errors.push(`${field} must be a non-empty array of non-empty strings when provided.`);
|
|
61
|
+
}
|
|
62
|
+
else if (new Set(value).size !== value.length) {
|
|
63
|
+
errors.push(`${field} must not contain duplicate entries.`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// Real-agent fields are validated against the RAW object so defaults can never look like an opt-in.
|
|
67
|
+
const agentId = raw.agentId;
|
|
68
|
+
if (agentId === undefined) {
|
|
69
|
+
if (raw.includeRealAgents === true)
|
|
70
|
+
errors.push("includeRealAgents requires agentId; real-agent mode is entered only by naming one provider.");
|
|
71
|
+
else if (raw.includeRealAgents !== undefined && typeof raw.includeRealAgents !== "boolean")
|
|
72
|
+
errors.push("includeRealAgents must be a boolean.");
|
|
73
|
+
if (raw.timeoutMs !== undefined)
|
|
74
|
+
errors.push("timeoutMs is only supported together with agentId.");
|
|
75
|
+
if (raw.kitCommand !== undefined)
|
|
76
|
+
errors.push("kitCommand is only supported together with agentId.");
|
|
77
|
+
if (raw.repairAttempts !== undefined) {
|
|
78
|
+
if (!isRepairAttemptsValue(raw.repairAttempts))
|
|
79
|
+
errors.push("repairAttempts must be 0, 1 or 2.");
|
|
80
|
+
else if (raw.repairAttempts > 0)
|
|
81
|
+
errors.push("repairAttempts greater than 0 requires real-agent mode (agentId together with includeRealAgents=true); deterministic-fixture mode has no repair.");
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
if (Array.isArray(agentId)) {
|
|
86
|
+
errors.push("agentId must name exactly one provider (codex or claude); multiple providers are not supported.");
|
|
87
|
+
}
|
|
88
|
+
else if (typeof agentId !== "string" || !AGENT_SUCCESS_RATE_REAL_AGENT_IDS.includes(agentId)) {
|
|
89
|
+
errors.push(`agentId must be one of: ${AGENT_SUCCESS_RATE_REAL_AGENT_IDS.join(", ")}.`);
|
|
90
|
+
}
|
|
91
|
+
if (raw.includeRealAgents !== true)
|
|
92
|
+
errors.push("agentId requires includeRealAgents to be exactly true.");
|
|
93
|
+
if (raw.timeoutMs !== undefined) {
|
|
94
|
+
const timeoutMs = raw.timeoutMs;
|
|
95
|
+
if (typeof timeoutMs !== "number" || !Number.isFinite(timeoutMs) || !Number.isInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs > AGENT_SUCCESS_RATE_MAX_AGENT_TIMEOUT_MS) {
|
|
96
|
+
errors.push(`timeoutMs must be a finite positive integer no greater than ${AGENT_SUCCESS_RATE_MAX_AGENT_TIMEOUT_MS}.`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (raw.repairAttempts !== undefined && !isRepairAttemptsValue(raw.repairAttempts))
|
|
100
|
+
errors.push("repairAttempts must be 0, 1 or 2.");
|
|
101
|
+
if (raw.kitCommand !== undefined) {
|
|
102
|
+
const kitCommand = raw.kitCommand;
|
|
103
|
+
let supported = false;
|
|
104
|
+
if (typeof kitCommand === "string" && kitCommand.trim()) {
|
|
105
|
+
try {
|
|
106
|
+
supported = parseCommandString(kitCommand).executable.length > 0;
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
supported = false;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (!supported)
|
|
113
|
+
errors.push("kitCommand must be a non-empty my-dev-kit command string.");
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
normalized.kitCommand = DEFAULT_AGENT_SUCCESS_KIT_COMMAND;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return errors.length > 0 ? invalidExperimentConfig(errors) : validExperimentConfig(normalized);
|
|
120
|
+
}
|
|
121
|
+
function isRepairAttemptsValue(value) {
|
|
122
|
+
return typeof value === "number" && AGENT_SUCCESS_RATE_REPAIR_ATTEMPT_VALUES.includes(value);
|
|
123
|
+
}
|
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
import { lstat, mkdtemp, readFile, readdir, rm } from "node:fs/promises";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { countEstimatedTokens, countTextChars } from "../../../core/countTokens.js";
|
|
5
|
+
import { resolveWithinRoot } from "../../../core/pathSafety.js";
|
|
6
|
+
import { compareCodeUnits } from "../../../evaluation/indexSnapshot.js";
|
|
7
|
+
import { normalizeRetrievedRepositoryPath } from "../../../evaluation/retrievalQuality/buildRetrievalEvidence.js";
|
|
8
|
+
import { buildContextPack } from "../contextPackGeneration/buildContextPack.js";
|
|
9
|
+
import { deriveSymbolSourceRange, findIndexedSymbolLine, interpretSymbolIndex, parseLookup, parseSearchHits, parseSlice, parseSource } from "../contextPackGeneration/packEvidence.js";
|
|
10
|
+
import { CONTEXT_PACK_SELECTION_POLICY_ID, GRAPH_DEPTH, MAX_SOURCE_LINES_PER_SLICE, MAX_SOURCE_SLICES, SEARCH_RESULT_LIMIT, isTestFilePath, selectRelevantSymbols, selectSeedNodes } from "../contextPackGeneration/packSelectionPolicy.js";
|
|
11
|
+
export const AGENT_SUCCESS_RAW_CONTEXT_POLICY_ID = "raw-source-glob-v1";
|
|
12
|
+
export const AGENT_SUCCESS_PACK_CONTEXT_POLICY_ID = CONTEXT_PACK_SELECTION_POLICY_ID;
|
|
13
|
+
export const DEFAULT_AGENT_SUCCESS_KIT_COMMAND = "npx @dailephd/my-dev-kit@latest";
|
|
14
|
+
const MAX_RAW_FILE_BYTES = 262_144;
|
|
15
|
+
const MAX_RAW_TOTAL_BYTES = 1_048_576;
|
|
16
|
+
const FORBIDDEN_DIRECTORIES = new Set([".git", "node_modules", "dist", "build", "coverage", "lab-output"]);
|
|
17
|
+
const lf = (text) => text.replace(/\r\n?/g, "\n");
|
|
18
|
+
function normalizeRoot(root) {
|
|
19
|
+
const normalized = normalizeRetrievedRepositoryPath(root);
|
|
20
|
+
return normalized === null ? null : normalized.replace(/\/+$/, "");
|
|
21
|
+
}
|
|
22
|
+
/** Pure eligibility policy applied to every candidate before any source is read or retrieved. */
|
|
23
|
+
export function isEligibleSourcePath(candidate, input) {
|
|
24
|
+
const normalized = normalizeRetrievedRepositoryPath(candidate);
|
|
25
|
+
if (normalized === null || normalized !== candidate)
|
|
26
|
+
return false;
|
|
27
|
+
const roots = input.sourceRoots.map(normalizeRoot).filter((root) => root !== null);
|
|
28
|
+
if (!roots.some((root) => normalized === root || normalized.startsWith(`${root}/`)))
|
|
29
|
+
return false;
|
|
30
|
+
if (isTestFilePath(normalized))
|
|
31
|
+
return false;
|
|
32
|
+
if (normalized.split("/").some((segment) => FORBIDDEN_DIRECTORIES.has(segment)))
|
|
33
|
+
return false;
|
|
34
|
+
return !input.excludedPaths.includes(normalized);
|
|
35
|
+
}
|
|
36
|
+
/** `file:<path>` and `symbol:<path>#<name>` node identities; anything else yields null and is dropped. */
|
|
37
|
+
export function sourcePathOfNodeId(nodeId) {
|
|
38
|
+
const match = /^(?:file|symbol):([^#]+)/.exec(nodeId);
|
|
39
|
+
return match ? normalizeRetrievedRepositoryPath(match[1]) : null;
|
|
40
|
+
}
|
|
41
|
+
export function buildContextTaskInput(task) {
|
|
42
|
+
const excluded = new Set();
|
|
43
|
+
for (const check of [...task.taskChecks, ...task.regressionChecks]) {
|
|
44
|
+
for (const arg of check.args) {
|
|
45
|
+
const normalized = normalizeRetrievedRepositoryPath(arg);
|
|
46
|
+
if (normalized !== null && !arg.startsWith("-") && /[./]/.test(arg))
|
|
47
|
+
excluded.add(normalized);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
caseId: task.id,
|
|
52
|
+
benchmarkProject: task.benchmarkProject,
|
|
53
|
+
title: task.title,
|
|
54
|
+
query: task.query,
|
|
55
|
+
sourceRoots: [...task.sourceRoots],
|
|
56
|
+
rawIncludeGlobs: [...task.rawIncludeGlobs],
|
|
57
|
+
excludedPaths: [...excluded].sort(compareCodeUnits)
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function unavailable(treatmentId, selectionPolicyId, reason, myDevKitVersion = null) {
|
|
61
|
+
return { treatmentId, availability: "unavailable", reason, selectionPolicyId, myDevKitVersion, includedSourceFiles: [], contextChars: 0, estimatedContextTokens: 0, text: null };
|
|
62
|
+
}
|
|
63
|
+
// ---------------------------------------------------------------------------------------------------------------
|
|
64
|
+
// raw-full-file
|
|
65
|
+
// ---------------------------------------------------------------------------------------------------------------
|
|
66
|
+
/** Directory names never entered while looking for raw source: trusted tests and generated/vendored trees. */
|
|
67
|
+
const SKIPPED_DIRECTORIES = new Set([...FORBIDDEN_DIRECTORIES, "tests", "test", "__tests__"]);
|
|
68
|
+
/**
|
|
69
|
+
* Standard glob semantics for the supported subset: `**` matches across directories (`**` followed by `/` matches zero
|
|
70
|
+
* or more directories), `*` and `?` stay within one path segment. Anything else is a literal. Returns null for a glob
|
|
71
|
+
* that is not a plain project-relative pattern.
|
|
72
|
+
*/
|
|
73
|
+
function globToRegExp(glob) {
|
|
74
|
+
const normalized = glob.replace(/\\/g, "/");
|
|
75
|
+
if (normalized.length === 0 || normalized.startsWith("/") || /^[A-Za-z]:/.test(normalized) || normalized.split("/").some((segment) => segment === ".."))
|
|
76
|
+
return null;
|
|
77
|
+
let pattern = "";
|
|
78
|
+
for (let index = 0; index < normalized.length; index += 1) {
|
|
79
|
+
const char = normalized[index];
|
|
80
|
+
if (char === "*" && normalized[index + 1] === "*") {
|
|
81
|
+
if (normalized[index + 2] === "/") {
|
|
82
|
+
pattern += "(?:.*/)?";
|
|
83
|
+
index += 2;
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
pattern += ".*";
|
|
87
|
+
index += 1;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
else if (char === "*")
|
|
91
|
+
pattern += "[^/]*";
|
|
92
|
+
else if (char === "?")
|
|
93
|
+
pattern += "[^/]";
|
|
94
|
+
else
|
|
95
|
+
pattern += char.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
96
|
+
}
|
|
97
|
+
return new RegExp(`^${pattern}$`);
|
|
98
|
+
}
|
|
99
|
+
/** Walks only the allowed source roots, so trusted-test and excluded directories are never even listed. */
|
|
100
|
+
async function listRawCandidates(projectRoot, input) {
|
|
101
|
+
const patterns = input.rawIncludeGlobs.map(globToRegExp);
|
|
102
|
+
if (patterns.some((pattern) => pattern === null))
|
|
103
|
+
return null;
|
|
104
|
+
const found = new Map();
|
|
105
|
+
const visit = async (relativeDirectory) => {
|
|
106
|
+
const entries = await readdir(resolveWithinRoot(projectRoot, relativeDirectory), { withFileTypes: true });
|
|
107
|
+
for (const entry of entries) {
|
|
108
|
+
if (entry.name.startsWith("."))
|
|
109
|
+
continue;
|
|
110
|
+
const relativePath = `${relativeDirectory}/${entry.name}`;
|
|
111
|
+
if (entry.isDirectory()) {
|
|
112
|
+
if (!SKIPPED_DIRECTORIES.has(entry.name))
|
|
113
|
+
await visit(relativePath);
|
|
114
|
+
}
|
|
115
|
+
else if ((entry.isFile() || entry.isSymbolicLink()) && patterns.some((pattern) => pattern.test(relativePath))) {
|
|
116
|
+
found.set(relativePath, { absolutePath: resolveWithinRoot(projectRoot, relativePath), relativePath });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
for (const root of input.sourceRoots.map(normalizeRoot)) {
|
|
121
|
+
if (root === null || root === ".")
|
|
122
|
+
return null;
|
|
123
|
+
await visit(root);
|
|
124
|
+
}
|
|
125
|
+
return [...found.values()];
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Every eligible source file matching the task's public glob selection, in code-unit order, LF-normalized. Reads only
|
|
129
|
+
* regular, non-link, text files below the allowed source roots; anything unreadable makes the context unavailable
|
|
130
|
+
* rather than partially fabricated.
|
|
131
|
+
*/
|
|
132
|
+
export async function buildRawFullFileContext(input, canonicalProjectRoot) {
|
|
133
|
+
const treatmentId = "raw-full-file";
|
|
134
|
+
const policy = AGENT_SUCCESS_RAW_CONTEXT_POLICY_ID;
|
|
135
|
+
let candidates;
|
|
136
|
+
try {
|
|
137
|
+
candidates = await listRawCandidates(canonicalProjectRoot, input);
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
candidates = null;
|
|
141
|
+
}
|
|
142
|
+
if (candidates === null)
|
|
143
|
+
return unavailable(treatmentId, policy, "raw-glob-resolution-failed");
|
|
144
|
+
const eligible = candidates.filter((file) => isEligibleSourcePath(file.relativePath, input)).sort((a, b) => compareCodeUnits(a.relativePath, b.relativePath));
|
|
145
|
+
if (eligible.length === 0)
|
|
146
|
+
return unavailable(treatmentId, policy, "no-eligible-source-files");
|
|
147
|
+
const sections = [];
|
|
148
|
+
let totalBytes = 0;
|
|
149
|
+
for (const file of eligible) {
|
|
150
|
+
try {
|
|
151
|
+
const stats = await lstat(resolveWithinRoot(canonicalProjectRoot, file.relativePath));
|
|
152
|
+
if (stats.isSymbolicLink() || !stats.isFile())
|
|
153
|
+
return unavailable(treatmentId, policy, "source-file-not-regular");
|
|
154
|
+
if (stats.size > MAX_RAW_FILE_BYTES)
|
|
155
|
+
return unavailable(treatmentId, policy, "source-file-too-large");
|
|
156
|
+
totalBytes += stats.size;
|
|
157
|
+
if (totalBytes > MAX_RAW_TOTAL_BYTES)
|
|
158
|
+
return unavailable(treatmentId, policy, "source-context-too-large");
|
|
159
|
+
const data = await readFile(file.absolutePath);
|
|
160
|
+
if (data.includes(0))
|
|
161
|
+
return unavailable(treatmentId, policy, "source-file-not-text");
|
|
162
|
+
sections.push(`=== FILE: ${file.relativePath} ===\n${lf(data.toString("utf8"))}\n`);
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
return unavailable(treatmentId, policy, "source-file-unreadable");
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
const text = sections.join("\n");
|
|
169
|
+
return {
|
|
170
|
+
treatmentId,
|
|
171
|
+
availability: "available",
|
|
172
|
+
reason: null,
|
|
173
|
+
selectionPolicyId: policy,
|
|
174
|
+
myDevKitVersion: null,
|
|
175
|
+
includedSourceFiles: eligible.map((file) => file.relativePath),
|
|
176
|
+
contextChars: countTextChars(text),
|
|
177
|
+
estimatedContextTokens: countEstimatedTokens(text),
|
|
178
|
+
text
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
const pad = (index) => String(index + 1).padStart(2, "0");
|
|
182
|
+
/**
|
|
183
|
+
* Builds the context-pack treatment. The index covers only the task's source roots of the clean canonical project, and
|
|
184
|
+
* every search hit, lookup result, graph neighbor, source candidate, symbol and call endpoint is filtered by the
|
|
185
|
+
* eligibility policy BEFORE it is selected or its source is retrieved. The pure v0.8.2 selection and rendering
|
|
186
|
+
* primitives are reused unchanged; the test-candidate input is always empty.
|
|
187
|
+
*/
|
|
188
|
+
export class AgentSuccessPackContextBuilder {
|
|
189
|
+
kitCommand;
|
|
190
|
+
dependencies;
|
|
191
|
+
workRoot = null;
|
|
192
|
+
version;
|
|
193
|
+
indexes = new Map();
|
|
194
|
+
constructor(kitCommand, dependencies) {
|
|
195
|
+
this.kitCommand = kitCommand;
|
|
196
|
+
this.dependencies = dependencies;
|
|
197
|
+
}
|
|
198
|
+
/** Removes the private work directory. Returns a bounded reason when it could not be removed. */
|
|
199
|
+
async dispose() {
|
|
200
|
+
const root = this.workRoot;
|
|
201
|
+
this.workRoot = null;
|
|
202
|
+
this.indexes.clear();
|
|
203
|
+
if (root === null)
|
|
204
|
+
return null;
|
|
205
|
+
try {
|
|
206
|
+
await rm(root, { recursive: true, force: true });
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
return "context work directory could not be removed.";
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
async ensureWorkRoot() {
|
|
214
|
+
if (this.workRoot === null)
|
|
215
|
+
this.workRoot = await mkdtemp(path.join(os.tmpdir(), "my-dev-kit-lab-asr-ctx-"));
|
|
216
|
+
return this.workRoot;
|
|
217
|
+
}
|
|
218
|
+
indexFor(input, canonicalProjectRoot) {
|
|
219
|
+
const key = `${canonicalProjectRoot}\0${[...input.sourceRoots].sort(compareCodeUnits).join("\0")}`;
|
|
220
|
+
let pending = this.indexes.get(key);
|
|
221
|
+
if (!pending) {
|
|
222
|
+
pending = this.buildIndex(input, canonicalProjectRoot, this.indexes.size);
|
|
223
|
+
this.indexes.set(key, pending);
|
|
224
|
+
}
|
|
225
|
+
return pending;
|
|
226
|
+
}
|
|
227
|
+
async buildIndex(input, canonicalProjectRoot, ordinal) {
|
|
228
|
+
try {
|
|
229
|
+
const root = await this.ensureWorkRoot();
|
|
230
|
+
const indexDir = path.join(root, `index-${ordinal}`, "base");
|
|
231
|
+
const commandsDir = path.join(root, `index-${ordinal}`, "commands");
|
|
232
|
+
const build = await this.dependencies.buildIndex({
|
|
233
|
+
target: { absoluteTargetRoot: canonicalProjectRoot, sourceRoots: [...input.sourceRoots] },
|
|
234
|
+
kitCommand: this.kitCommand,
|
|
235
|
+
indexDir,
|
|
236
|
+
commandsDir: path.join(commandsDir, "index"),
|
|
237
|
+
requireKit: false,
|
|
238
|
+
callGraph: true
|
|
239
|
+
});
|
|
240
|
+
if (!build.ok)
|
|
241
|
+
return { ok: false };
|
|
242
|
+
if (this.version === undefined) {
|
|
243
|
+
const tool = await this.dependencies.probeVersion({ kitCommand: this.kitCommand, commandsDir: path.join(root, "version") });
|
|
244
|
+
this.version = tool.version ?? null;
|
|
245
|
+
}
|
|
246
|
+
let symbolIndex = null;
|
|
247
|
+
try {
|
|
248
|
+
symbolIndex = interpretSymbolIndex(await this.dependencies.readSymbolIndex(indexDir));
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
symbolIndex = null;
|
|
252
|
+
}
|
|
253
|
+
return { ok: true, indexDir, commandsDir, symbolIndex };
|
|
254
|
+
}
|
|
255
|
+
catch {
|
|
256
|
+
return { ok: false };
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
async build(input, canonicalProjectRoot) {
|
|
260
|
+
const treatmentId = "context-pack";
|
|
261
|
+
const policy = AGENT_SUCCESS_PACK_CONTEXT_POLICY_ID;
|
|
262
|
+
const project = await this.indexFor(input, canonicalProjectRoot);
|
|
263
|
+
if (!project.ok)
|
|
264
|
+
return unavailable(treatmentId, policy, "project-index-failed", this.version ?? null);
|
|
265
|
+
const version = this.version ?? null;
|
|
266
|
+
const failed = (reason) => unavailable(treatmentId, policy, reason, version);
|
|
267
|
+
const { indexDir, symbolIndex } = project;
|
|
268
|
+
const commandsDir = path.join(project.commandsDir, input.caseId.replace(/[^A-Za-z0-9._-]/g, "-"));
|
|
269
|
+
const run = (commandId, extraArgs) => this.dependencies.runCommand({ commandId, commandString: this.kitCommand, cwd: process.cwd(), outDir: commandsDir, extraArgs });
|
|
270
|
+
const eligiblePath = (candidate) => candidate !== null && isEligibleSourcePath(candidate, input);
|
|
271
|
+
const eligibleNode = (nodeId) => eligiblePath(sourcePathOfNodeId(nodeId));
|
|
272
|
+
// 1. One search; candidates outside the allowed source are removed before seeds are chosen.
|
|
273
|
+
let hits;
|
|
274
|
+
try {
|
|
275
|
+
const search = await run("search", ["search", "--index", indexDir, "--query", input.query, "--limit", String(SEARCH_RESULT_LIMIT), "--json"]);
|
|
276
|
+
const parsed = search.ok ? parseSearchHits(search.stdout, SEARCH_RESULT_LIMIT) : null;
|
|
277
|
+
if (!parsed || !parsed.ok)
|
|
278
|
+
return failed("retrieval-failed");
|
|
279
|
+
hits = parsed.value.filter((hit) => eligiblePath(hit.path) && eligibleNode(hit.nodeId));
|
|
280
|
+
}
|
|
281
|
+
catch {
|
|
282
|
+
return failed("retrieval-failed");
|
|
283
|
+
}
|
|
284
|
+
const fileCandidates = [];
|
|
285
|
+
const symbolCandidates = [];
|
|
286
|
+
const callCandidates = [];
|
|
287
|
+
const lineByNodeId = new Map();
|
|
288
|
+
const extraNotes = [];
|
|
289
|
+
for (const hit of hits) {
|
|
290
|
+
if (hit.path !== null)
|
|
291
|
+
fileCandidates.push({ path: hit.path, origin: "search", rank: hit.rank });
|
|
292
|
+
if (hit.kind === "symbol") {
|
|
293
|
+
symbolCandidates.push({ name: hit.label ?? hit.nodeId.slice(hit.nodeId.indexOf("#") + 1), nodeId: hit.nodeId, file: hit.path, line: null, origin: "search", rank: hit.rank });
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
// 2. Seeds in upstream order; lookup then depth-1 slice per seed.
|
|
297
|
+
const seeds = selectSeedNodes(hits.map((hit) => ({ nodeId: hit.nodeId, origin: "search", rank: hit.rank })));
|
|
298
|
+
extraNotes.push(...seeds.notes);
|
|
299
|
+
let graphIncomplete = false;
|
|
300
|
+
for (const [index, seed] of seeds.items.entries()) {
|
|
301
|
+
try {
|
|
302
|
+
const lookup = await run(`lookup-${pad(index)}`, ["lookup", "--index", indexDir, "--node", seed.nodeId, "--depth", String(GRAPH_DEPTH), "--json"]);
|
|
303
|
+
const parsed = lookup.ok ? parseLookup(lookup.stdout) : null;
|
|
304
|
+
if (parsed && parsed.ok) {
|
|
305
|
+
const node = parsed.value.node;
|
|
306
|
+
if (eligiblePath(node.path) && eligibleNode(node.id)) {
|
|
307
|
+
fileCandidates.push({ path: node.path, origin: "lookup", rank: seed.rank });
|
|
308
|
+
if (node.kind === "symbol" && node.symbolName !== null) {
|
|
309
|
+
symbolCandidates.push({ name: node.symbolName, nodeId: node.id, file: node.path, line: node.line, origin: "lookup", rank: seed.rank });
|
|
310
|
+
if (node.line !== null)
|
|
311
|
+
lineByNodeId.set(node.id, node.line);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
else
|
|
316
|
+
graphIncomplete = true;
|
|
317
|
+
}
|
|
318
|
+
catch {
|
|
319
|
+
graphIncomplete = true;
|
|
320
|
+
}
|
|
321
|
+
try {
|
|
322
|
+
const slice = await run(`slice-${pad(index)}`, ["slice", "--index", indexDir, "--node", seed.nodeId, "--depth", String(GRAPH_DEPTH), "--direction", "both", "--json"]);
|
|
323
|
+
const parsed = slice.ok ? parseSlice(slice.stdout) : null;
|
|
324
|
+
if (parsed && parsed.ok) {
|
|
325
|
+
for (const node of parsed.value.nodes) {
|
|
326
|
+
if (!eligiblePath(node.path) || !eligibleNode(node.id))
|
|
327
|
+
continue;
|
|
328
|
+
fileCandidates.push({ path: node.path, origin: "graph", rank: seed.rank });
|
|
329
|
+
if (node.kind === "symbol" && node.symbolName !== null) {
|
|
330
|
+
symbolCandidates.push({ name: node.symbolName, nodeId: node.id, file: node.path, line: node.line, origin: "graph", rank: seed.rank });
|
|
331
|
+
if (node.line !== null && !lineByNodeId.has(node.id))
|
|
332
|
+
lineByNodeId.set(node.id, node.line);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
for (const edge of parsed.value.edges) {
|
|
336
|
+
if (eligibleNode(edge.source) && eligibleNode(edge.target))
|
|
337
|
+
callCandidates.push({ fromNodeId: edge.source, toNodeId: edge.target, kind: edge.kind });
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
else
|
|
341
|
+
graphIncomplete = true;
|
|
342
|
+
}
|
|
343
|
+
catch {
|
|
344
|
+
graphIncomplete = true;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
// 3. Bounded source only for already-filtered, selected symbols.
|
|
348
|
+
const selectedSymbols = selectRelevantSymbols(symbolCandidates).items;
|
|
349
|
+
const sliceCandidates = [];
|
|
350
|
+
let sourceIncomplete = false;
|
|
351
|
+
let sourceCount = 0;
|
|
352
|
+
for (const symbol of selectedSymbols) {
|
|
353
|
+
if (sourceCount >= MAX_SOURCE_SLICES)
|
|
354
|
+
break;
|
|
355
|
+
if (!eligiblePath(symbol.file))
|
|
356
|
+
continue;
|
|
357
|
+
const line = (symbol.nodeId !== null ? lineByNodeId.get(symbol.nodeId) : undefined) ?? (symbolIndex ? findIndexedSymbolLine(symbolIndex, symbol.file, symbol.name) : null);
|
|
358
|
+
const range = symbolIndex && line !== null ? deriveSymbolSourceRange(symbolIndex, symbol.file, line) : null;
|
|
359
|
+
if (!range && symbol.nodeId === null)
|
|
360
|
+
continue;
|
|
361
|
+
sourceCount += 1;
|
|
362
|
+
const nodeId = symbol.nodeId;
|
|
363
|
+
try {
|
|
364
|
+
const args = range
|
|
365
|
+
? ["source", "--index", indexDir, "--file", symbol.file, "--start", String(range.startLine), "--end", String(range.endLine), "--json"]
|
|
366
|
+
: ["source", "--index", indexDir, "--node", nodeId, "--max-lines", String(MAX_SOURCE_LINES_PER_SLICE), "--json"];
|
|
367
|
+
const source = await run(`source-${pad(sourceCount - 1)}`, args);
|
|
368
|
+
const parsed = source.ok ? parseSource(source.stdout) : null;
|
|
369
|
+
if (!parsed || !parsed.ok) {
|
|
370
|
+
sourceIncomplete = true;
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
const value = parsed.value;
|
|
374
|
+
if (range?.capped)
|
|
375
|
+
extraNotes.push("source-slice-cap-reached");
|
|
376
|
+
sliceCandidates.push({
|
|
377
|
+
file: symbol.file,
|
|
378
|
+
nodeId,
|
|
379
|
+
symbolName: symbol.name,
|
|
380
|
+
startLine: value.startLine,
|
|
381
|
+
endLine: value.endLine,
|
|
382
|
+
text: value.content,
|
|
383
|
+
boundaryKnown: value.boundaryKnown,
|
|
384
|
+
truncated: range?.capped === true,
|
|
385
|
+
continuationAvailable: range ? range.capped : !value.eof,
|
|
386
|
+
rank: symbol.rank
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
catch {
|
|
390
|
+
sourceIncomplete = true;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
const boundaryUnknown = sliceCandidates.some((candidate) => !candidate.boundaryKnown);
|
|
394
|
+
// 4. Availability is decided here; composition is the pure v0.8.2 builder with an empty test-candidate input.
|
|
395
|
+
const available = { availability: "available", reason: null };
|
|
396
|
+
const graphSection = graphIncomplete ? { availability: "partial", reason: "graph-step-incomplete" } : available;
|
|
397
|
+
const sourceSection = sourceIncomplete
|
|
398
|
+
? { availability: "partial", reason: "source-step-incomplete" }
|
|
399
|
+
: boundaryUnknown
|
|
400
|
+
? { availability: "partial", reason: "symbol-end-unknown" }
|
|
401
|
+
: available;
|
|
402
|
+
const packReasonBase = graphIncomplete ? "graph-step-incomplete" : sourceIncomplete ? "source-step-incomplete" : boundaryUnknown ? "symbol-end-unknown" : null;
|
|
403
|
+
const packAvailabilityBase = packReasonBase === null ? "available" : "partial";
|
|
404
|
+
let pack;
|
|
405
|
+
try {
|
|
406
|
+
pack = buildContextPack({
|
|
407
|
+
caseId: input.caseId,
|
|
408
|
+
benchmarkProject: input.benchmarkProject,
|
|
409
|
+
taskLocality: null,
|
|
410
|
+
task: { title: input.title, summary: input.query },
|
|
411
|
+
myDevKitVersion: version ?? "unavailable",
|
|
412
|
+
fileCandidates,
|
|
413
|
+
symbolCandidates,
|
|
414
|
+
sourceSliceCandidates: sliceCandidates,
|
|
415
|
+
callCandidates,
|
|
416
|
+
testCandidates: [],
|
|
417
|
+
evidenceNotes: extraNotes,
|
|
418
|
+
sectionAvailability: {
|
|
419
|
+
files: graphSection,
|
|
420
|
+
symbols: graphSection,
|
|
421
|
+
sourceSlices: sourceSection,
|
|
422
|
+
callRelationships: graphSection,
|
|
423
|
+
tests: { availability: "unavailable", reason: "not-supplied" },
|
|
424
|
+
evidenceNotes: available
|
|
425
|
+
},
|
|
426
|
+
availability: packAvailabilityBase,
|
|
427
|
+
reason: packReasonBase
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
catch {
|
|
431
|
+
return failed("pack-construction-failed");
|
|
432
|
+
}
|
|
433
|
+
// 5. Defense in depth: nothing outside the allowed source may be present in the finished pack.
|
|
434
|
+
const violations = [
|
|
435
|
+
...pack.files.map((file) => file.path),
|
|
436
|
+
...pack.sourceSlices.map((slice) => slice.file),
|
|
437
|
+
...pack.symbols.map((symbol) => symbol.file).filter((file) => file !== null)
|
|
438
|
+
].filter((candidate) => !eligiblePath(candidate));
|
|
439
|
+
const endpointViolation = pack.callRelationships.some((call) => !eligibleNode(call.fromNodeId) || !eligibleNode(call.toNodeId));
|
|
440
|
+
if (violations.length > 0 || endpointViolation || pack.tests.length > 0)
|
|
441
|
+
return failed("context-isolation-violation");
|
|
442
|
+
if (pack.sourceSlices.length === 0)
|
|
443
|
+
return failed("no-source-slices");
|
|
444
|
+
const includedSourceFiles = [...new Set([...pack.files.map((file) => file.path), ...pack.sourceSlices.map((slice) => slice.file)])];
|
|
445
|
+
const degraded = symbolIndex === null && pack.availability === "available";
|
|
446
|
+
return {
|
|
447
|
+
treatmentId,
|
|
448
|
+
availability: degraded ? "partial" : pack.availability,
|
|
449
|
+
reason: degraded ? "symbol-index-unavailable" : pack.reason,
|
|
450
|
+
selectionPolicyId: pack.selectionPolicy.id,
|
|
451
|
+
myDevKitVersion: version,
|
|
452
|
+
includedSourceFiles,
|
|
453
|
+
contextChars: pack.size.totalChars,
|
|
454
|
+
estimatedContextTokens: pack.size.totalEstimatedTokens,
|
|
455
|
+
text: pack.renderedText
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Distinctive hidden values (reference patch, trusted-test paths, behavior-fact text and hyphenated identifiers) that
|
|
461
|
+
* must never appear in a prompt. Read from the task BEFORE it is sealed; used only to refuse a leaking prompt.
|
|
462
|
+
*/
|
|
463
|
+
export function collectForbiddenAgentValues(task) {
|
|
464
|
+
const values = [];
|
|
465
|
+
const add = (label, value) => {
|
|
466
|
+
if (typeof value === "string" && value.trim().length >= 6)
|
|
467
|
+
values.push({ label, value });
|
|
468
|
+
};
|
|
469
|
+
const fixture = task.deterministicFixture;
|
|
470
|
+
if (fixture) {
|
|
471
|
+
add("deterministicFixture.patch", fixture.patch);
|
|
472
|
+
add("deterministicFixture.id", fixture.id);
|
|
473
|
+
add("deterministicFixture.notes", fixture.notes);
|
|
474
|
+
}
|
|
475
|
+
for (const check of [...task.taskChecks, ...task.regressionChecks]) {
|
|
476
|
+
add("verificationCheck.id", /-/.test(check.id) ? check.id : undefined);
|
|
477
|
+
for (const arg of check.args)
|
|
478
|
+
if (!arg.startsWith("-") && /[./]/.test(arg))
|
|
479
|
+
add("verificationCheck.path", arg);
|
|
480
|
+
}
|
|
481
|
+
for (const fact of task.behaviorFacts) {
|
|
482
|
+
add("behaviorFact.text", fact.text);
|
|
483
|
+
add("behaviorFact.id", /-/.test(fact.id) ? fact.id : undefined);
|
|
484
|
+
}
|
|
485
|
+
for (const file of task.protectedFiles)
|
|
486
|
+
if (isTestFilePath(file))
|
|
487
|
+
add("protectedFile.test", file);
|
|
488
|
+
return values;
|
|
489
|
+
}
|
|
490
|
+
/** Returns the labels (never the values) of forbidden content found in a finished prompt. */
|
|
491
|
+
export function findPromptLeaks(promptText, forbidden) {
|
|
492
|
+
const found = new Set();
|
|
493
|
+
for (const entry of forbidden)
|
|
494
|
+
if (promptText.includes(entry.value))
|
|
495
|
+
found.add(entry.label);
|
|
496
|
+
return [...found].sort(compareCodeUnits);
|
|
497
|
+
}
|