@dailephd/my-dev-kit-lab 0.6.2 → 0.7.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 +26 -0
- package/README.md +11 -8
- package/benchmarks/contracts/context-window-scaling-cases.json +220 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/README.md +14 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-1/carrier-depot-catalog-01.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-1/course-module-catalog-06.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-1/library-shelf-catalog-07.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-1/sensor-station-catalog-05.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-1/staff-rota-catalog-03.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-1/supplier-contract-catalog-02.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-1/vehicle-route-catalog-04.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-2/archive-box-catalog-12.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-2/clinic-ward-catalog-08.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-2/greenhouse-bay-catalog-13.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-2/harbor-berth-catalog-10.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-2/kitchen-section-catalog-15.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-2/market-stall-catalog-09.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-2/station-platform-catalog-14.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-2/workshop-bench-catalog-11.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/archive-box-catalog-28.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/carrier-depot-catalog-17.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/clinic-ward-catalog-24.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/course-module-catalog-22.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/greenhouse-bay-catalog-29.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/harbor-berth-catalog-26.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/kitchen-section-catalog-31.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/library-shelf-catalog-23.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/market-stall-catalog-25.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/sensor-station-catalog-21.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/staff-rota-catalog-19.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/station-platform-catalog-30.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/supplier-contract-catalog-18.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/vehicle-route-catalog-20.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/warehouse-zone-catalog-16.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/workshop-bench-catalog-27.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/archive-box-catalog-44.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/carrier-depot-catalog-33.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/carrier-depot-catalog-49.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/clinic-ward-catalog-40.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/clinic-ward-catalog-56.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/course-module-catalog-38.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/course-module-catalog-54.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/greenhouse-bay-catalog-45.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/harbor-berth-catalog-42.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/harbor-berth-catalog-58.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/kitchen-section-catalog-47.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/library-shelf-catalog-39.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/library-shelf-catalog-55.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/market-stall-catalog-41.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/market-stall-catalog-57.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/sensor-station-catalog-37.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/sensor-station-catalog-53.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/staff-rota-catalog-35.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/staff-rota-catalog-51.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/station-platform-catalog-46.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/supplier-contract-catalog-34.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/supplier-contract-catalog-50.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/vehicle-route-catalog-36.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/vehicle-route-catalog-52.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/warehouse-zone-catalog-32.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/warehouse-zone-catalog-48.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/workshop-bench-catalog-43.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/tasks/auditRedaction.ts +25 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/tasks/inventoryReservation.ts +26 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/tasks/invoiceTotals.ts +25 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/tasks/shippingQuote.ts +32 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/tests/tasks/auditRedaction.test.ts +17 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/tests/tasks/inventoryReservation.test.ts +19 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/tests/tasks/invoiceTotals.test.ts +14 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/tests/tasks/shippingQuote.test.ts +19 -0
- package/dist/src/cli/help.js +27 -10
- package/dist/src/commands/runExperimentDescribeCommand.js +5 -2
- package/dist/src/commands/runExperimentRunCommand.js +64 -8
- package/dist/src/evaluation/benchmarkMetadata.js +5 -1
- package/dist/src/evaluation/parseAgentAnswer.js +172 -24
- package/dist/src/evaluation/runMyDevKitRetrieval.js +120 -7
- package/dist/src/experiments/defaultRegistry.js +2 -0
- package/dist/src/experiments/plugins/contextWindowScaling/cliBudgets.js +31 -0
- package/dist/src/experiments/plugins/contextWindowScaling/config.js +84 -0
- package/dist/src/experiments/plugins/contextWindowScaling/contextBudget.js +43 -0
- package/dist/src/experiments/plugins/contextWindowScaling/evaluation.js +36 -0
- package/dist/src/experiments/plugins/contextWindowScaling/execution.js +187 -0
- package/dist/src/experiments/plugins/contextWindowScaling/executionArtifact.js +15 -0
- package/dist/src/experiments/plugins/contextWindowScaling/executionArtifactReader.js +186 -0
- package/dist/src/experiments/plugins/contextWindowScaling/index.js +14 -0
- package/dist/src/experiments/plugins/contextWindowScaling/metadata.js +12 -0
- package/dist/src/experiments/plugins/contextWindowScaling/metrics.js +149 -0
- package/dist/src/experiments/plugins/contextWindowScaling/plugin.js +185 -0
- package/dist/src/experiments/plugins/contextWindowScaling/projectProfile.js +51 -0
- package/dist/src/experiments/plugins/contextWindowScaling/relevantFiles.js +76 -0
- package/dist/src/experiments/plugins/contextWindowScaling/successEvidence.js +60 -0
- package/dist/src/experiments/plugins/contextWindowScaling/types.js +1 -0
- package/dist/src/experiments/plugins/incrementalChangeStaleness/comparisonV2.js +49 -0
- package/dist/src/experiments/plugins/incrementalChangeStaleness/config.js +5 -4
- package/dist/src/experiments/plugins/incrementalChangeStaleness/disposableTarget.js +20 -2
- package/dist/src/experiments/plugins/incrementalChangeStaleness/execution.js +42 -16
- package/dist/src/experiments/plugins/incrementalChangeStaleness/executionArtifact.js +4 -4
- package/dist/src/experiments/plugins/incrementalChangeStaleness/executionArtifactV2.js +289 -0
- package/dist/src/experiments/plugins/incrementalChangeStaleness/executionV2.js +195 -0
- package/dist/src/experiments/plugins/incrementalChangeStaleness/indexClone.js +76 -0
- package/dist/src/experiments/plugins/incrementalChangeStaleness/lifecycle.js +64 -32
- package/dist/src/experiments/plugins/incrementalChangeStaleness/lifecyclePolicy.js +6 -4
- package/dist/src/experiments/plugins/incrementalChangeStaleness/lifecyclePolicyV2.js +78 -0
- package/dist/src/experiments/plugins/incrementalChangeStaleness/lifecycleV2.js +298 -0
- package/dist/src/experiments/plugins/incrementalChangeStaleness/plugin.js +213 -12
- package/dist/src/experiments/plugins/incrementalChangeStaleness/treatmentSessionV2.js +5 -0
- package/dist/src/experiments/plugins/index.js +1 -0
- package/dist/src/experiments/plugins/warmIndexReuse/agentEvaluation.js +1 -1
- package/dist/src/experiments/plugins/warmIndexReuse/warmIndexSession.js +3 -1
- package/dist/src/mobile/android/advancedSecurity/candidateEvidence.js +9 -2
- package/dist/src/mobile/android/advancedSecurity/redaction.js +19 -10
- package/dist/src/mobile/android/advancedSecurity/secretCandidates/analyzeSecretCandidates.js +12 -2
- package/dist/src/mobile/android/advancedSecurity/signingConfiguration/analyzeSigningConfiguration.js +1 -1
- package/dist/src/mobile/android/advancedSecurity/signingConfiguration/extractSigningConfigurations.js +7 -7
- package/dist/src/plots/buildContextWindowScalingPlotData.js +96 -0
- package/dist/src/plots/index.js +1 -0
- package/dist/src/plots/writePlotArtifacts.js +12 -3
- package/dist/src/report/experiments/buildContextWindowScalingReport.js +84 -0
- package/dist/src/report/experiments/buildIncrementalChangeStalenessPluginReport.js +33 -0
- package/dist/src/report/experiments/buildIncrementalChangeStalenessReport.js +1 -1
- package/dist/src/report/experiments/buildIncrementalChangeStalenessReportV2.js +80 -0
- package/dist/src/report/experiments/buildPluginExperimentReport.js +24 -4
- package/dist/src/report/experiments/contextWindowScalingReportModel.js +3 -0
- package/dist/src/report/experiments/incrementalChangeStalenessReportModelV2.js +3 -0
- package/dist/src/report/experiments/incrementalChangeStalenessReportRowsV2.js +146 -0
- package/dist/src/report/experiments/index.js +7 -0
- package/dist/src/report/experiments/renderContextWindowScalingHtml.js +99 -0
- package/dist/src/report/experiments/renderContextWindowScalingText.js +108 -0
- package/dist/src/report/experiments/renderIncrementalChangeStalenessHtml.js +4 -0
- package/dist/src/report/experiments/renderIncrementalChangeStalenessHtmlV2.js +111 -0
- package/dist/src/report/experiments/renderIncrementalChangeStalenessTextV2.js +96 -0
- package/dist/src/report/experiments/renderPluginExperimentReportHtml.js +3 -0
- package/dist/src/report/experiments/renderPluginExperimentReportText.js +14 -0
- package/dist/src/securityValidation/staticScans/codeql.js +8 -8
- package/docs/METRICS.md +58 -0
- package/examples/tutorial-browser/prepare.mjs +13 -13
- package/examples/tutorial-browser/server.mjs +13 -13
- package/package.json +1 -1
|
@@ -7,10 +7,10 @@ import { toAffectedNeighborhoodMetricValues } from "../../../evaluation/affected
|
|
|
7
7
|
import { sanitizePathSegment } from "../../outputPaths.js";
|
|
8
8
|
import { summarizeExperimentRun } from "../../results.js";
|
|
9
9
|
import { defaultIncrementalChangeStalenessConfig, incrementalChangeStalenessConfigDefinition, validateIncrementalChangeStalenessConfig } from "./config.js";
|
|
10
|
-
import { DEFAULT_INCREMENTAL_CHANGE_STALENESS_RUNTIME_ROOT_RELATIVE,
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
10
|
+
import { DEFAULT_INCREMENTAL_CHANGE_STALENESS_RUNTIME_ROOT_RELATIVE, INCREMENTAL_CHANGE_STALENESS_V2_TREATMENT_IDS, INCREMENTAL_CHANGE_STALENESS_V2_TREATMENT_INTENTS, removeIncrementalChangeStalenessRuntimeRoot } from "./disposableTarget.js";
|
|
11
|
+
import { buildIncrementalChangeStalenessExecutionArtifactV2, writeIncrementalChangeStalenessExecutionArtifactV2 } from "./executionArtifactV2.js";
|
|
12
|
+
import { executeIncrementalChangeStalenessScenarioV2 } from "./executionV2.js";
|
|
13
|
+
import { prepareIncrementalChangeStalenessScenarioLifecycleV2 } from "./lifecycleV2.js";
|
|
14
14
|
import { BENCHMARK_PROJECT_PROFILES_PATH } from "./scenarioCatalog.js";
|
|
15
15
|
import { resolveIncrementalChangeStalenessScenarios } from "./scenarioSelection.js";
|
|
16
16
|
export const INCREMENTAL_CHANGE_STALENESS_PLUGIN_ID = "incremental-change-staleness";
|
|
@@ -21,13 +21,39 @@ export const INCREMENTAL_CHANGE_STALENESS_RUN_OWNER_MARKER = ".incremental-chang
|
|
|
21
21
|
export const incrementalChangeStalenessMetadata = {
|
|
22
22
|
id: INCREMENTAL_CHANGE_STALENESS_PLUGIN_ID,
|
|
23
23
|
name: "Incremental Change Staleness",
|
|
24
|
-
description: "Compares
|
|
24
|
+
description: "Compares no refresh, my-dev-kit changed-files incremental refresh, my-dev-kit affected-neighborhood incremental refresh, and full refresh after the same deterministic controlled source change. Comparisons are scoped to the observed evidence (correctness and required-file presence); full refresh is a reference treatment, not an asserted winner, and a partial treatment may truthfully fall back to a full rebuild.",
|
|
25
25
|
schemaVersion: "1.0.0",
|
|
26
26
|
status: "experimental",
|
|
27
27
|
supportedTargets: ["self"],
|
|
28
28
|
supportedOutputs: ["json", "html"]
|
|
29
29
|
};
|
|
30
|
+
export const CHANGED_FILES_REFRESH_VARIANT_ID = "changed-files-refresh";
|
|
31
|
+
export const AFFECTED_NEIGHBORHOOD_REFRESH_VARIANT_ID = "affected-neighborhood-refresh";
|
|
32
|
+
/** Public v0.6.3 treatments, in the fixed lifecycle order. Factual descriptions only; no treatment is called best or preferred. */
|
|
30
33
|
const TREATMENT_VARIANTS = [
|
|
34
|
+
{
|
|
35
|
+
id: STALE_INDEX_VARIANT_ID,
|
|
36
|
+
name: "No refresh (stale index)",
|
|
37
|
+
description: "Keeps the pre-mutation baseline index and performs no post-mutation refresh."
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: CHANGED_FILES_REFRESH_VARIANT_ID,
|
|
41
|
+
name: "Changed-files refresh",
|
|
42
|
+
description: "Uses my-dev-kit's incremental changed-files refresh after the controlled mutation; upstream may truthfully fall back to a full rebuild."
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: AFFECTED_NEIGHBORHOOD_REFRESH_VARIANT_ID,
|
|
46
|
+
name: "Affected-neighborhood refresh",
|
|
47
|
+
description: "Uses my-dev-kit's affected-neighborhood incremental refresh after the controlled mutation; upstream may truthfully fall back to a full rebuild."
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: FULL_REFRESH_VARIANT_ID,
|
|
51
|
+
name: "Full refresh",
|
|
52
|
+
description: "Builds a complete post-mutation index and uses it as the comparison reference."
|
|
53
|
+
}
|
|
54
|
+
];
|
|
55
|
+
/** The released v0.6.2 two-treatment variants, kept only for the historical V1 run mapper. */
|
|
56
|
+
const V1_TREATMENT_VARIANTS = [
|
|
31
57
|
{
|
|
32
58
|
id: STALE_INDEX_VARIANT_ID,
|
|
33
59
|
name: "Stale index",
|
|
@@ -78,7 +104,7 @@ export const incrementalChangeStalenessPlugin = {
|
|
|
78
104
|
metadata: incrementalChangeStalenessMetadata,
|
|
79
105
|
defaultConfig: defaultIncrementalChangeStalenessConfig,
|
|
80
106
|
configDefinition: incrementalChangeStalenessConfigDefinition,
|
|
81
|
-
supportedVariants: [...
|
|
107
|
+
supportedVariants: [...INCREMENTAL_CHANGE_STALENESS_V2_TREATMENT_IDS],
|
|
82
108
|
validateConfig: validateIncrementalChangeStalenessConfig,
|
|
83
109
|
async run(context) {
|
|
84
110
|
if (!context.target.isSelf) {
|
|
@@ -94,14 +120,14 @@ export const incrementalChangeStalenessPlugin = {
|
|
|
94
120
|
const projectProfiles = await readBenchmarkProjectProfiles(path.resolve(context.toolRoot, BENCHMARK_PROJECT_PROFILES_PATH), context.toolRoot);
|
|
95
121
|
const executions = [];
|
|
96
122
|
for (const resolved of resolvedScenarios) {
|
|
97
|
-
const lifecycle = await
|
|
123
|
+
const lifecycle = await prepareIncrementalChangeStalenessScenarioLifecycleV2({
|
|
98
124
|
repoRoot: context.toolRoot,
|
|
99
125
|
runOwnedRoot,
|
|
100
126
|
scenario: resolved.scenario,
|
|
101
127
|
baseCase: resolved.baseCase,
|
|
102
128
|
kitCommand: context.config.kitCommand
|
|
103
129
|
});
|
|
104
|
-
const execution = await
|
|
130
|
+
const execution = await executeIncrementalChangeStalenessScenarioV2({
|
|
105
131
|
repoRoot: context.toolRoot,
|
|
106
132
|
scenario: resolved.scenario,
|
|
107
133
|
lifecycle,
|
|
@@ -113,15 +139,15 @@ export const incrementalChangeStalenessPlugin = {
|
|
|
113
139
|
executions.push(execution);
|
|
114
140
|
}
|
|
115
141
|
const outDir = context.outputRoot ?? path.resolve(context.toolRoot, context.config.outDir);
|
|
116
|
-
const artifact =
|
|
142
|
+
const artifact = buildIncrementalChangeStalenessExecutionArtifactV2({
|
|
117
143
|
runId: context.runId,
|
|
118
144
|
pluginId: INCREMENTAL_CHANGE_STALENESS_PLUGIN_ID,
|
|
119
145
|
executions
|
|
120
146
|
});
|
|
121
147
|
// The artifact is written before the framework's separate `cleanup` phase runs, so it is
|
|
122
148
|
// written before the run-owned ephemeral index/target state is ever destroyed.
|
|
123
|
-
const artifactPath = await
|
|
124
|
-
return
|
|
149
|
+
const artifactPath = await writeIncrementalChangeStalenessExecutionArtifactV2(outDir, artifact);
|
|
150
|
+
return mapIncrementalChangeStalenessExecutionsToRunV2({
|
|
125
151
|
runId: context.runId,
|
|
126
152
|
startedAt,
|
|
127
153
|
completedAt: new Date().toISOString(),
|
|
@@ -239,7 +265,7 @@ export function mapIncrementalChangeStalenessExecutionsToRun(args) {
|
|
|
239
265
|
completedAt: args.completedAt,
|
|
240
266
|
status,
|
|
241
267
|
target: args.target,
|
|
242
|
-
variants:
|
|
268
|
+
variants: V1_TREATMENT_VARIANTS.map((variant) => ({ ...variant })),
|
|
243
269
|
cases,
|
|
244
270
|
metrics: [
|
|
245
271
|
{ id: "incremental-change-staleness-scenario-count", name: "Scenario count", value: args.executions.length, unit: "count", description: "Selected scenarios in this run." },
|
|
@@ -272,3 +298,178 @@ export function mapIncrementalChangeStalenessExecutionsToRun(args) {
|
|
|
272
298
|
run.summary = summarizeExperimentRun(run);
|
|
273
299
|
return run;
|
|
274
300
|
}
|
|
301
|
+
// ---------------------------------------------------------------------------
|
|
302
|
+
// v0.6.3 four-treatment mapping. Maps already-executed V2 evidence; recomputes nothing.
|
|
303
|
+
// ---------------------------------------------------------------------------
|
|
304
|
+
const UPSTREAM_REFRESH_METRIC_SPECS = [
|
|
305
|
+
{ id: "upstream-refresh-fresh-extraction-file-count", name: "Upstream fresh-extraction files", field: "freshExtractionFileCount", description: "Files my-dev-kit freshly re-extracted during the refresh invocation (upstream execution evidence, not Lab task overlap)." },
|
|
306
|
+
{ id: "upstream-refresh-reused-file-count", name: "Upstream reused files", field: "reusedFileCount", description: "Files my-dev-kit reused from the previous index during the refresh invocation (upstream execution evidence)." },
|
|
307
|
+
{ id: "upstream-refresh-forced-neighbor-reanalysis-file-count", name: "Upstream forced-neighbor reanalysis files", field: "forcedNeighborReanalysisFileCount", description: "Neighbor files my-dev-kit forced to re-analyze during the refresh invocation (upstream execution evidence)." },
|
|
308
|
+
{ id: "upstream-refresh-seed-file-count", name: "Upstream seed files", field: "seedFileCount", description: "Seed files my-dev-kit selected for the affected-neighborhood refresh (upstream execution evidence; absent when upstream reported none)." },
|
|
309
|
+
{ id: "upstream-refresh-seed-symbol-count", name: "Upstream seed symbols", field: "seedSymbolCount", description: "Seed symbols my-dev-kit selected for the affected-neighborhood refresh (upstream execution evidence; absent when upstream reported none)." },
|
|
310
|
+
{ id: "upstream-refresh-affected-node-count", name: "Upstream affected nodes", field: "affectedNodeCount", description: "Graph nodes my-dev-kit treated as affected during the refresh (upstream execution evidence, distinct from the Lab affected-neighborhood metrics)." },
|
|
311
|
+
{ id: "upstream-refresh-affected-edge-count", name: "Upstream affected edges", field: "affectedEdgeCount", description: "Graph edges my-dev-kit treated as affected during the refresh (upstream execution evidence, distinct from the Lab affected-neighborhood metrics)." }
|
|
312
|
+
];
|
|
313
|
+
function upstreamRefreshMetrics(treatment, caseId) {
|
|
314
|
+
const refresh = treatment.refreshExecution;
|
|
315
|
+
if (refresh.kind !== "incremental")
|
|
316
|
+
return [];
|
|
317
|
+
const metrics = [];
|
|
318
|
+
for (const spec of UPSTREAM_REFRESH_METRIC_SPECS) {
|
|
319
|
+
const value = refresh.incrementalRefresh[spec.field];
|
|
320
|
+
// Null upstream fields are unavailable evidence, never a zero-valued metric.
|
|
321
|
+
if (typeof value === "number")
|
|
322
|
+
metrics.push(metric(spec.id, spec.name, spec.description, "count", value, treatment.treatmentId, caseId));
|
|
323
|
+
}
|
|
324
|
+
return metrics;
|
|
325
|
+
}
|
|
326
|
+
function comparisonMetadata(execution, treatmentId) {
|
|
327
|
+
const comparison = execution.referenceComparisons.find((entry) => entry.candidateTreatmentId === treatmentId);
|
|
328
|
+
if (!comparison)
|
|
329
|
+
return {};
|
|
330
|
+
if (comparison.kind === "stale-risk") {
|
|
331
|
+
return {
|
|
332
|
+
comparisonCorrectnessRelation: comparison.comparison.correctnessRelation,
|
|
333
|
+
comparisonRequiredFileEvidenceRelation: comparison.comparison.requiredFileEvidenceRelation,
|
|
334
|
+
staleRiskClassification: comparison.comparison.staleRiskClassification
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
return {
|
|
338
|
+
referenceComparisonCorrectnessRelation: comparison.correctnessRelation,
|
|
339
|
+
referenceComparisonRequiredFileEvidenceRelation: comparison.requiredFileEvidenceRelation,
|
|
340
|
+
referenceComparisonClassification: comparison.classification
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
function treatmentOutcomeV2(execution, treatmentId) {
|
|
344
|
+
const caseId = execution.scenarioId;
|
|
345
|
+
const treatmentIntent = INCREMENTAL_CHANGE_STALENESS_V2_TREATMENT_INTENTS[treatmentId];
|
|
346
|
+
const base = { id: `${caseId}:${treatmentId}`, caseId, variantId: treatmentId, artifacts: [] };
|
|
347
|
+
const treatment = execution.treatments.find((entry) => entry.treatmentId === treatmentId);
|
|
348
|
+
if (execution.status === "failed" || !treatment) {
|
|
349
|
+
return {
|
|
350
|
+
...base,
|
|
351
|
+
status: "failed",
|
|
352
|
+
metrics: [],
|
|
353
|
+
warnings: [],
|
|
354
|
+
failures: [
|
|
355
|
+
{
|
|
356
|
+
code: "incremental-change-staleness-scenario-failed",
|
|
357
|
+
message: execution.failureReason ?? "Scenario execution failed before treatment evidence was produced.",
|
|
358
|
+
variantId: treatmentId,
|
|
359
|
+
caseId,
|
|
360
|
+
recoverable: false
|
|
361
|
+
}
|
|
362
|
+
],
|
|
363
|
+
metadata: { treatmentId, treatmentIntent, scenarioStatus: execution.status }
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
const status = treatment.status === "completed" ? "completed" : treatment.status === "failed" ? "failed" : "partial";
|
|
367
|
+
const refresh = treatment.refreshExecution;
|
|
368
|
+
const incremental = refresh.kind === "incremental" ? refresh.incrementalRefresh : null;
|
|
369
|
+
return {
|
|
370
|
+
...base,
|
|
371
|
+
status,
|
|
372
|
+
metrics: [...treatmentMetrics(treatment, caseId), ...upstreamRefreshMetrics(treatment, caseId)],
|
|
373
|
+
warnings: treatment.retrievalStatus === "failed" || treatment.fakeAgent?.status === "failed"
|
|
374
|
+
? [{ code: "incremental-change-staleness-treatment-partial", message: treatment.failureReason ?? "Treatment retrieval or fake-agent evaluation did not fully complete.", variantId: treatmentId, caseId }]
|
|
375
|
+
: [],
|
|
376
|
+
failures: [],
|
|
377
|
+
metadata: {
|
|
378
|
+
treatmentId,
|
|
379
|
+
treatmentIntent,
|
|
380
|
+
activeIndexPhase: treatment.activeIndexPhase,
|
|
381
|
+
treatmentStatus: treatment.status,
|
|
382
|
+
retrievalStatus: treatment.retrievalStatus,
|
|
383
|
+
fakeAgentStatus: treatment.fakeAgent?.status ?? "not-run",
|
|
384
|
+
requiredFileEvidenceStatus: treatment.requiredFileEvidence.status,
|
|
385
|
+
affectedNeighborhoodRelationship: treatment.affectedNeighborhood.relationship,
|
|
386
|
+
affectedNeighborhoodReindexRecommendation: treatment.affectedNeighborhood.reindexRecommendation,
|
|
387
|
+
refreshKind: refresh.kind,
|
|
388
|
+
refreshRealization: refresh.realization,
|
|
389
|
+
// Upstream categorical evidence only for incremental treatments; never invented for the others.
|
|
390
|
+
...(incremental
|
|
391
|
+
? {
|
|
392
|
+
requestedScope: incremental.requestedScope,
|
|
393
|
+
appliedScope: incremental.appliedScope,
|
|
394
|
+
selectionStatus: incremental.selectionStatus,
|
|
395
|
+
fallbackReason: incremental.fallbackReason
|
|
396
|
+
}
|
|
397
|
+
: {}),
|
|
398
|
+
...comparisonMetadata(execution, treatmentId)
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
function countByRealization(executions, treatmentId, realization) {
|
|
403
|
+
return executions.filter((execution) => execution.treatments.find((treatment) => treatment.treatmentId === treatmentId)?.refreshExecution.realization === realization).length;
|
|
404
|
+
}
|
|
405
|
+
export function mapIncrementalChangeStalenessExecutionsToRunV2(args) {
|
|
406
|
+
const cases = args.executions.map((execution) => {
|
|
407
|
+
const partialClass = (candidate) => {
|
|
408
|
+
const comparison = execution.referenceComparisons.find((entry) => entry.candidateTreatmentId === candidate);
|
|
409
|
+
return comparison && comparison.kind === "partial-refresh" ? comparison.classification : null;
|
|
410
|
+
};
|
|
411
|
+
const stale = execution.referenceComparisons.find((entry) => entry.candidateTreatmentId === "stale-index");
|
|
412
|
+
return {
|
|
413
|
+
id: execution.scenarioId,
|
|
414
|
+
name: `${execution.scenarioId} (${execution.category})`,
|
|
415
|
+
// Exactly four outcomes per scenario, in the fixed lifecycle order (never a "raw" outcome).
|
|
416
|
+
outcomes: INCREMENTAL_CHANGE_STALENESS_V2_TREATMENT_IDS.map((treatmentId) => treatmentOutcomeV2(execution, treatmentId)),
|
|
417
|
+
metadata: {
|
|
418
|
+
category: execution.category,
|
|
419
|
+
benchmarkProject: execution.benchmarkProjectId,
|
|
420
|
+
baseCaseId: execution.baseCaseId,
|
|
421
|
+
scenarioStatus: execution.status,
|
|
422
|
+
staleRiskClassification: stale && stale.kind === "stale-risk" ? stale.comparison.staleRiskClassification : null,
|
|
423
|
+
changedFilesReferenceClassification: partialClass("changed-files-refresh"),
|
|
424
|
+
affectedNeighborhoodReferenceClassification: partialClass("affected-neighborhood-refresh")
|
|
425
|
+
}
|
|
426
|
+
};
|
|
427
|
+
});
|
|
428
|
+
const outcomeStatuses = cases.flatMap((experimentCase) => experimentCase.outcomes.map((outcome) => outcome.status));
|
|
429
|
+
const status = outcomeStatuses.length > 0 && outcomeStatuses.every((value) => value === "failed")
|
|
430
|
+
? "failed"
|
|
431
|
+
: outcomeStatuses.every((value) => value === "completed")
|
|
432
|
+
? "completed"
|
|
433
|
+
: "partial";
|
|
434
|
+
const count = (id, name, value, description) => ({ id, name, value, unit: "count", description });
|
|
435
|
+
const run = {
|
|
436
|
+
runId: args.runId,
|
|
437
|
+
pluginId: INCREMENTAL_CHANGE_STALENESS_PLUGIN_ID,
|
|
438
|
+
startedAt: args.startedAt,
|
|
439
|
+
completedAt: args.completedAt,
|
|
440
|
+
status,
|
|
441
|
+
target: args.target,
|
|
442
|
+
variants: TREATMENT_VARIANTS.map((variant) => ({ ...variant })),
|
|
443
|
+
cases,
|
|
444
|
+
metrics: [
|
|
445
|
+
count("incremental-change-staleness-scenario-count", "Scenario count", args.executions.length, "Selected scenarios in this run."),
|
|
446
|
+
count("incremental-change-staleness-observed-regression-count", "Observed stale regression count", args.executions.filter((execution) => {
|
|
447
|
+
const stale = execution.referenceComparisons.find((entry) => entry.candidateTreatmentId === "stale-index");
|
|
448
|
+
return stale?.kind === "stale-risk" && stale.comparison.staleRiskClassification === "observed-stale-regression";
|
|
449
|
+
}).length, "Scenarios whose no-refresh treatment was classified observed-stale-regression against full refresh."),
|
|
450
|
+
count("incremental-change-staleness-changed-files-applied-partial-count", "Changed-files applied-partial count", countByRealization(args.executions, "changed-files-refresh", "APPLIED_PARTIAL"), "Scenarios where the changed-files refresh was applied as a partial refresh."),
|
|
451
|
+
count("incremental-change-staleness-changed-files-fallback-full-count", "Changed-files fallback-full count", countByRealization(args.executions, "changed-files-refresh", "FALLBACK_FULL"), "Scenarios where the changed-files refresh fell back to a full rebuild."),
|
|
452
|
+
count("incremental-change-staleness-affected-neighborhood-applied-partial-count", "Affected-neighborhood applied-partial count", countByRealization(args.executions, "affected-neighborhood-refresh", "APPLIED_PARTIAL"), "Scenarios where the affected-neighborhood refresh was applied as a partial refresh."),
|
|
453
|
+
count("incremental-change-staleness-affected-neighborhood-fallback-full-count", "Affected-neighborhood fallback-full count", countByRealization(args.executions, "affected-neighborhood-refresh", "FALLBACK_FULL"), "Scenarios where the affected-neighborhood refresh fell back to a full rebuild.")
|
|
454
|
+
],
|
|
455
|
+
artifacts: [
|
|
456
|
+
{
|
|
457
|
+
id: "incremental-change-staleness-execution",
|
|
458
|
+
label: "Incremental-change-staleness execution evidence",
|
|
459
|
+
path: args.artifactPath,
|
|
460
|
+
kind: "artifact",
|
|
461
|
+
mimeType: "application/json",
|
|
462
|
+
description: "Per-scenario four-treatment execution, refresh-realization, and full-refresh reference-comparison evidence, without raw context bodies."
|
|
463
|
+
}
|
|
464
|
+
],
|
|
465
|
+
warnings: [],
|
|
466
|
+
failures: [],
|
|
467
|
+
metadata: {
|
|
468
|
+
kitCommand: args.kitCommand,
|
|
469
|
+
scenarioIds: args.executions.map((execution) => execution.scenarioId),
|
|
470
|
+
executionArtifactPath: args.artifactPath
|
|
471
|
+
}
|
|
472
|
+
};
|
|
473
|
+
run.summary = summarizeExperimentRun(run);
|
|
474
|
+
return run;
|
|
475
|
+
}
|
|
@@ -41,7 +41,7 @@ export async function evaluateWarmIndexFakeAgents(options) {
|
|
|
41
41
|
}
|
|
42
42
|
return result;
|
|
43
43
|
}
|
|
44
|
-
async function evaluateFakeSide(args) {
|
|
44
|
+
export async function evaluateFakeSide(args) {
|
|
45
45
|
const promptStrategy = SIDE_STRATEGIES[args.variantId];
|
|
46
46
|
const base = { variantId: args.variantId, agentId: "fake-agent", promptStrategy };
|
|
47
47
|
try {
|
|
@@ -5,7 +5,9 @@ import { loadAffectedNeighborhoodGraphEvidence } from "../../../evaluation/affec
|
|
|
5
5
|
import { captureIndexSnapshot } from "../../../evaluation/indexSnapshot.js";
|
|
6
6
|
/**
|
|
7
7
|
* Builds the index once through the shared `buildMyDevKitIndex` owner. A build that reports
|
|
8
|
-
* success but leaves no index directory is not treated as a prepared session.
|
|
8
|
+
* success but leaves no index directory is not treated as a prepared session. `mode` is optional
|
|
9
|
+
* and defaults to an ordinary full build; only callers that explicitly request an incremental
|
|
10
|
+
* build mode get one.
|
|
9
11
|
*/
|
|
10
12
|
export async function prepareWarmIndexSession(options) {
|
|
11
13
|
const build = await buildMyDevKitIndex(options);
|
|
@@ -19,14 +19,21 @@ function buildCandidateEvidenceId(ruleId, location, fingerprint) {
|
|
|
19
19
|
const locationPart = [location.path, location.line !== undefined ? `l${location.line}` : undefined, location.column !== undefined ? `c${location.column}` : undefined]
|
|
20
20
|
.filter((part) => Boolean(part))
|
|
21
21
|
.join(":");
|
|
22
|
-
|
|
22
|
+
const payloadStart = fingerprint.indexOf(":") + 1;
|
|
23
|
+
return [ruleId, slug(locationPart), fingerprint.slice(payloadStart, payloadStart + 16)].join("--");
|
|
23
24
|
}
|
|
24
25
|
// Never accepts a raw secret value as a plain string parameter name to
|
|
25
26
|
// discourage accidental logging at call sites — `rawValue` is redacted and
|
|
26
27
|
// fingerprinted here, once, and the raw value is never stored on the
|
|
27
28
|
// returned object.
|
|
28
29
|
export function makeCandidateEvidence(input) {
|
|
29
|
-
const fingerprint = fingerprintCandidateValue(input.rawValue
|
|
30
|
+
const fingerprint = fingerprintCandidateValue(input.rawValue, {
|
|
31
|
+
ruleId: input.ruleId,
|
|
32
|
+
sourcePath: input.location.path,
|
|
33
|
+
line: input.location.line,
|
|
34
|
+
column: input.location.column,
|
|
35
|
+
purpose: "candidate-evidence"
|
|
36
|
+
});
|
|
30
37
|
const redactedPreview = redactedPreviewForCandidate(input.rawValue);
|
|
31
38
|
const id = buildCandidateEvidenceId(input.ruleId, input.location, fingerprint);
|
|
32
39
|
return {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { scryptSync } from "node:crypto";
|
|
2
|
+
import path from "node:path";
|
|
2
3
|
import { stripUnsafeControlChars } from "../../../securityValidation/attackScenarios/exploitEvidence.js";
|
|
3
4
|
// ---------------------------------------------------------------------------
|
|
4
5
|
// v0.4.1 Batch 1 — redaction and fingerprint primitives for later Android
|
|
@@ -13,8 +14,8 @@ import { stripUnsafeControlChars } from "../../../securityValidation/attackScena
|
|
|
13
14
|
// payload). Android candidate evidence has a different, narrower need: a
|
|
14
15
|
// human reviewing a static-candidate finding benefits from a short prefix/
|
|
15
16
|
// suffix so they can recognize *which* secret in their own source it is
|
|
16
|
-
// without the full value
|
|
17
|
-
//
|
|
17
|
+
// without retaining the full value — so this module adds that
|
|
18
|
+
// prefix/suffix-preserving preview and a one-way fingerprint on
|
|
18
19
|
// top of the shared control-character stripping, instead of creating a
|
|
19
20
|
// second, competing full-redaction implementation.
|
|
20
21
|
// ---------------------------------------------------------------------------
|
|
@@ -42,13 +43,21 @@ export function redactedPreviewForCandidate(input, options = {}) {
|
|
|
42
43
|
const suffix = bounded.slice(-edgeLength);
|
|
43
44
|
return `${prefix}${MASK}${suffix}`;
|
|
44
45
|
}
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
export function fingerprintCandidateValue(input) {
|
|
46
|
+
// Deterministic, one-way evidence identity. Scrypt makes guessing more costly
|
|
47
|
+
// than a fast hash, but low-entropy values are not cryptographically
|
|
48
|
+
// unrecoverable. The context salt separates equivalent values at distinct
|
|
49
|
+
// evidence locations without storing a secret salt or the raw value.
|
|
50
|
+
export function fingerprintCandidateValue(input, context) {
|
|
50
51
|
if (input === undefined)
|
|
51
52
|
return "unavailable";
|
|
52
|
-
const
|
|
53
|
-
|
|
53
|
+
const contextSalt = Buffer.from(JSON.stringify([
|
|
54
|
+
"my-dev-kit-lab/android-candidate-fingerprint/v2",
|
|
55
|
+
context.ruleId,
|
|
56
|
+
path.posix.normalize(context.sourcePath.replaceAll("\\", "/")),
|
|
57
|
+
context.line ?? null,
|
|
58
|
+
context.column ?? null,
|
|
59
|
+
context.purpose
|
|
60
|
+
]), "utf8");
|
|
61
|
+
const derived = scryptSync(input, contextSalt, 32, { N: 16384, r: 8, p: 1, maxmem: 64 * 1024 * 1024 });
|
|
62
|
+
return `scrypt-v1:${derived.toString("hex")}`;
|
|
54
63
|
}
|
package/dist/src/mobile/android/advancedSecurity/secretCandidates/analyzeSecretCandidates.js
CHANGED
|
@@ -51,7 +51,6 @@ export function analyzeSecretCandidateFile(targetRoot, file) {
|
|
|
51
51
|
const fileKind = fileKindFor(file.relativePath);
|
|
52
52
|
for (const block of matchPrivateKeyBlocks(file.content)) {
|
|
53
53
|
const location = buildAndroidSourceLocation(targetRoot, file.absolutePath, { line: block.line });
|
|
54
|
-
const fingerprint = fingerprintCandidateValue(block.rawBlock);
|
|
55
54
|
if (!block.terminated) {
|
|
56
55
|
candidates.push(makeCandidateEvidence({
|
|
57
56
|
ruleId: "android-secret-private-key-candidate",
|
|
@@ -66,6 +65,12 @@ export function analyzeSecretCandidateFile(targetRoot, file) {
|
|
|
66
65
|
}));
|
|
67
66
|
continue;
|
|
68
67
|
}
|
|
68
|
+
const fingerprint = fingerprintCandidateValue(block.rawBlock, {
|
|
69
|
+
ruleId: "android-secret-private-key-candidate",
|
|
70
|
+
sourcePath: location.path,
|
|
71
|
+
line: location.line,
|
|
72
|
+
purpose: "private-key-finding"
|
|
73
|
+
});
|
|
69
74
|
findings.push(makeAndroidFinding({
|
|
70
75
|
ruleId: "android-secret-private-key-candidate",
|
|
71
76
|
title: `Private key material found (${block.headerLabel})`,
|
|
@@ -98,7 +103,12 @@ export function analyzeSecretCandidateFile(targetRoot, file) {
|
|
|
98
103
|
}));
|
|
99
104
|
continue;
|
|
100
105
|
}
|
|
101
|
-
const fingerprint = fingerprintCandidateValue(match.rawValue
|
|
106
|
+
const fingerprint = fingerprintCandidateValue(match.rawValue, {
|
|
107
|
+
ruleId: classification.ruleId,
|
|
108
|
+
sourcePath: location.path,
|
|
109
|
+
line: location.line,
|
|
110
|
+
purpose: `credential-finding:${classification.sensitiveDataCategory}`
|
|
111
|
+
});
|
|
102
112
|
findings.push(makeAndroidFinding({
|
|
103
113
|
ruleId: classification.ruleId,
|
|
104
114
|
title: `Hardcoded ${match.identifier} literal found`,
|
package/dist/src/mobile/android/advancedSecurity/signingConfiguration/analyzeSigningConfiguration.js
CHANGED
|
@@ -149,7 +149,7 @@ export function analyzeModuleSigningConfiguration(targetRoot, gradleModule, buil
|
|
|
149
149
|
const buildFileRelativePath = gradleModule.buildFilePath ?? modulePath;
|
|
150
150
|
const buildFileAbsolutePath = path.join(targetRoot, buildFileRelativePath);
|
|
151
151
|
const location = loc(targetRoot, buildFileAbsolutePath);
|
|
152
|
-
const signingConfigs = extractSigningConfigurations(buildFileText);
|
|
152
|
+
const signingConfigs = extractSigningConfigurations(buildFileText, { sourcePath: location.path });
|
|
153
153
|
const literalCredentialConfigNames = new Set();
|
|
154
154
|
for (const config of signingConfigs) {
|
|
155
155
|
const storePasswordResult = analyzeCredential("storePassword", config.storePassword, config.name, modulePath, location, buildFileRelativePath);
|
|
@@ -44,17 +44,17 @@ function extractStoreFileValue(content) {
|
|
|
44
44
|
return { state: "literal", literalValue: fileWrapped[1] };
|
|
45
45
|
return extractPathValue(content, ["storeFile"]);
|
|
46
46
|
}
|
|
47
|
-
function extractCredentialValue(content, keys) {
|
|
47
|
+
function extractCredentialValue(content, keys, context) {
|
|
48
48
|
const result = extractLiteral(content, keys);
|
|
49
49
|
const state = classifyExpressionState(result);
|
|
50
50
|
if (state === "literal" && result.value !== undefined) {
|
|
51
|
-
return { state, redactedPreview: redactedPreviewForCandidate(result.value), fingerprint: fingerprintCandidateValue(result.value) };
|
|
51
|
+
return { state, redactedPreview: redactedPreviewForCandidate(result.value), fingerprint: fingerprintCandidateValue(result.value, context) };
|
|
52
52
|
}
|
|
53
53
|
if (result.raw !== undefined)
|
|
54
54
|
return { state, rawExpression: boundedRawExpression(result.raw) };
|
|
55
55
|
return { state: "missing" };
|
|
56
56
|
}
|
|
57
|
-
function extractSigningConfigEntry(name, content) {
|
|
57
|
+
function extractSigningConfigEntry(name, content, sourcePath) {
|
|
58
58
|
const v1 = extractBooleanLiteral(content, ["enableV1Signing", "v1SigningEnabled"]);
|
|
59
59
|
const v2 = extractBooleanLiteral(content, ["enableV2Signing", "v2SigningEnabled"]);
|
|
60
60
|
const v3 = extractBooleanLiteral(content, ["enableV3Signing", "v3SigningEnabled"]);
|
|
@@ -62,9 +62,9 @@ function extractSigningConfigEntry(name, content) {
|
|
|
62
62
|
return {
|
|
63
63
|
name,
|
|
64
64
|
storeFile: extractStoreFileValue(content),
|
|
65
|
-
storePassword: extractCredentialValue(content, ["storePassword"]),
|
|
65
|
+
storePassword: extractCredentialValue(content, ["storePassword"], { ruleId: "android-signing-password-literal", sourcePath, purpose: `signing-credential:${name}:storePassword` }),
|
|
66
66
|
keyAlias: extractPathValue(content, ["keyAlias"]),
|
|
67
|
-
keyPassword: extractCredentialValue(content, ["keyPassword"]),
|
|
67
|
+
keyPassword: extractCredentialValue(content, ["keyPassword"], { ruleId: "android-signing-password-literal", sourcePath, purpose: `signing-credential:${name}:keyPassword` }),
|
|
68
68
|
enableV1Signing: v1.value,
|
|
69
69
|
enableV2Signing: v2.value,
|
|
70
70
|
enableV3Signing: v3.value,
|
|
@@ -73,9 +73,9 @@ function extractSigningConfigEntry(name, content) {
|
|
|
73
73
|
}
|
|
74
74
|
// Extracts every named entry inside a module build file's `signingConfigs { }`
|
|
75
75
|
// block. Returns an empty array (never throws) when no such block exists.
|
|
76
|
-
export function extractSigningConfigurations(rawBuildFileText) {
|
|
76
|
+
export function extractSigningConfigurations(rawBuildFileText, context) {
|
|
77
77
|
const text = stripComments(rawBuildFileText);
|
|
78
78
|
return extractNamedSubBlocks(text, "signingConfigs")
|
|
79
|
-
.map(({ name, content }) => extractSigningConfigEntry(name, content))
|
|
79
|
+
.map(({ name, content }) => extractSigningConfigEntry(name, content, context.sourcePath))
|
|
80
80
|
.sort((a, b) => a.name.localeCompare(b.name));
|
|
81
81
|
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { CONTEXT_WINDOW_SCALING_EXECUTION_ARTIFACT_FILE } from "../experiments/plugins/contextWindowScaling/executionArtifact.js";
|
|
5
|
+
import { parseContextWindowScalingExecutionArtifact } from "../experiments/plugins/contextWindowScaling/executionArtifactReader.js";
|
|
6
|
+
import { aggregateContextWindowScaling } from "../experiments/plugins/contextWindowScaling/metrics.js";
|
|
7
|
+
import { STANDARD_CONTEXT_BUDGETS } from "../experiments/plugins/contextWindowScaling/contextBudget.js";
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// Context-window-scaling plot data. Maps values already calculated by
|
|
10
|
+
// aggregateContextWindowScaling() (carried on ContextWindowScalingRun.aggregate) to plot
|
|
11
|
+
// points. Nothing is recalculated; a null aggregate value becomes a skipped point, never 0.
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
export const CONTEXT_WINDOW_SCALING_PLOT_IDS = [
|
|
14
|
+
"context-window-scaling-context-size",
|
|
15
|
+
"context-window-scaling-success-rate-by-budget",
|
|
16
|
+
"context-window-scaling-correctness-by-budget",
|
|
17
|
+
];
|
|
18
|
+
export const CONTEXT_WINDOW_SCALING_SKIP_REASONS = {
|
|
19
|
+
contextSize: "context-size-unavailable",
|
|
20
|
+
successRate: "success-rate-unavailable",
|
|
21
|
+
correctness: "correctness-unavailable",
|
|
22
|
+
};
|
|
23
|
+
/** Standard budgets read as 8k/16k/32k/64k; any other budget keeps its exact integer form. */
|
|
24
|
+
export function formatContextBudgetLabel(budget) {
|
|
25
|
+
return STANDARD_CONTEXT_BUDGETS.includes(budget) ? `${budget / 1024}k` : String(budget);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Returns the aggregate rebuilt from `<experimentDir>/context-window-scaling-execution.json`, or null
|
|
29
|
+
* when that artifact is absent. A present artifact is validated (schema version and structure) and
|
|
30
|
+
* aggregated by the canonical aggregateContextWindowScaling(); malformed input fails, never falls back.
|
|
31
|
+
*/
|
|
32
|
+
export async function readContextWindowScalingPlotSource(experimentDir) {
|
|
33
|
+
const artifactPath = path.join(experimentDir, CONTEXT_WINDOW_SCALING_EXECUTION_ARTIFACT_FILE);
|
|
34
|
+
if (!existsSync(artifactPath))
|
|
35
|
+
return null;
|
|
36
|
+
let parsed;
|
|
37
|
+
try {
|
|
38
|
+
parsed = JSON.parse(await readFile(artifactPath, "utf8"));
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
throw new Error(`Invalid context-window-scaling execution artifact (${artifactPath}): not valid JSON (${error instanceof Error ? error.message : String(error)}).`);
|
|
42
|
+
}
|
|
43
|
+
const artifact = parseContextWindowScalingExecutionArtifact(parsed);
|
|
44
|
+
return {
|
|
45
|
+
aggregate: aggregateContextWindowScaling({ contextBudgets: artifact.contextBudgets, cases: artifact.cases }),
|
|
46
|
+
generatedAt: artifact.completedAt,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
export function buildContextWindowScalingPlotData(args) {
|
|
50
|
+
const { aggregate } = args;
|
|
51
|
+
const skippedPoints = [];
|
|
52
|
+
const series = (id, title, xLabel, yLabel) => ({
|
|
53
|
+
id,
|
|
54
|
+
title,
|
|
55
|
+
xLabel,
|
|
56
|
+
yLabel,
|
|
57
|
+
kind: "scatter",
|
|
58
|
+
points: [],
|
|
59
|
+
warnings: [],
|
|
60
|
+
});
|
|
61
|
+
const contextSize = series(CONTEXT_WINDOW_SCALING_PLOT_IDS[0], "Raw vs Retrieved Context Size", "Case ordinal (canonical case order)", "Estimated context tokens");
|
|
62
|
+
const successRate = series(CONTEXT_WINDOW_SCALING_PLOT_IDS[1], "Success Rate by Context Budget", "Context budget (estimated tokens)", "Success rate (percent)");
|
|
63
|
+
const correctness = series(CONTEXT_WINDOW_SCALING_PLOT_IDS[2], "Correctness by Context Budget", "Context budget (estimated tokens)", "Mean correctness score");
|
|
64
|
+
const add = (plot, x, value, group, label, reason, metadata) => {
|
|
65
|
+
if (value !== null && Number.isFinite(value)) {
|
|
66
|
+
plot.points.push({ x, y: value, group, label, metadata });
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
skippedPoints.push({ plotId: plot.id, label, reason });
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
// Case ordinal follows the aggregate's canonical case order (first appearance of each caseId).
|
|
73
|
+
const caseOrdinals = new Map();
|
|
74
|
+
for (const summary of aggregate.caseTreatmentContextSummaries) {
|
|
75
|
+
if (!caseOrdinals.has(summary.caseId))
|
|
76
|
+
caseOrdinals.set(summary.caseId, caseOrdinals.size + 1);
|
|
77
|
+
}
|
|
78
|
+
for (const summary of aggregate.caseTreatmentContextSummaries) {
|
|
79
|
+
add(contextSize, caseOrdinals.get(summary.caseId), summary.contextStatus === "available" ? summary.estimatedTokens : null, summary.variantId, `${summary.caseId} ${summary.variantId}`, CONTEXT_WINDOW_SCALING_SKIP_REASONS.contextSize, { caseId: summary.caseId });
|
|
80
|
+
}
|
|
81
|
+
for (const summary of aggregate.budgetTreatmentSummaries) {
|
|
82
|
+
const label = `${summary.variantId} ${formatContextBudgetLabel(summary.contextBudgetTokens)}`;
|
|
83
|
+
const metadata = { contextBudgetTokens: summary.contextBudgetTokens };
|
|
84
|
+
add(successRate, summary.contextBudgetTokens, summary.successRatePercent, summary.variantId, label, CONTEXT_WINDOW_SCALING_SKIP_REASONS.successRate, metadata);
|
|
85
|
+
add(correctness, summary.contextBudgetTokens, summary.meanCorrectnessScore, summary.variantId, label, CONTEXT_WINDOW_SCALING_SKIP_REASONS.correctness, metadata);
|
|
86
|
+
}
|
|
87
|
+
const plots = [contextSize, successRate, correctness];
|
|
88
|
+
const warnings = [];
|
|
89
|
+
for (const plot of plots) {
|
|
90
|
+
if (plot.points.length === 0) {
|
|
91
|
+
plot.warnings.push("No comparable data available.");
|
|
92
|
+
warnings.push(`${plot.title}: no comparable data available.`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return { generatedAt: args.generatedAt, sourceExperimentDir: args.experimentDir, plots, skippedPoints, warnings };
|
|
96
|
+
}
|
package/dist/src/plots/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { resolveWithinRoot } from "../core/pathSafety.js";
|
|
4
|
+
import { buildContextWindowScalingPlotData, readContextWindowScalingPlotSource } from "./buildContextWindowScalingPlotData.js";
|
|
4
5
|
import { buildExperimentPlotData } from "./buildExperimentPlotData.js";
|
|
5
6
|
import { buildWarmIndexPlotData, readWarmIndexPlotSource } from "./buildWarmIndexPlotData.js";
|
|
6
7
|
import { renderSvgChart } from "./renderSvgChart.js";
|
|
@@ -16,10 +17,18 @@ export async function writePlotArtifacts(options) {
|
|
|
16
17
|
// A warm-index-reuse plugin output directory is detected by its plugin report; every other
|
|
17
18
|
// directory keeps the legacy controlled-experiment plot path unchanged.
|
|
18
19
|
const experimentDir = path.resolve(options.repoRoot ?? process.cwd(), options.experimentDir);
|
|
20
|
+
// A context-window-scaling directory is detected by its V1 execution artifact; evidence for more
|
|
21
|
+
// than one plugin family in the same directory is ambiguous and fails instead of choosing one.
|
|
22
|
+
const scalingSource = await readContextWindowScalingPlotSource(experimentDir);
|
|
19
23
|
const warmSection = await readWarmIndexPlotSource(experimentDir);
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
24
|
+
if (scalingSource && warmSection) {
|
|
25
|
+
throw new Error(`Ambiguous experiment directory for plots (${experimentDir}): it contains both context-window-scaling and warm-index-reuse evidence.`);
|
|
26
|
+
}
|
|
27
|
+
const data = scalingSource
|
|
28
|
+
? buildContextWindowScalingPlotData({ aggregate: scalingSource.aggregate, experimentDir, generatedAt: scalingSource.generatedAt })
|
|
29
|
+
: warmSection
|
|
30
|
+
? buildWarmIndexPlotData({ section: warmSection, experimentDir })
|
|
31
|
+
: await buildExperimentPlotData({ experimentDir: options.experimentDir, repoRoot: options.repoRoot });
|
|
23
32
|
return writePlotArtifactsFromData({ data, outDir: options.outDir });
|
|
24
33
|
}
|
|
25
34
|
export async function writePlotArtifactsFromData(options) {
|