@dailephd/my-dev-kit-lab 0.8.1 → 0.8.2
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 -1
- package/README.md +5 -4
- package/dist/src/cli/help.js +16 -1
- package/dist/src/commands/runExperimentDescribeCommand.js +8 -1
- package/dist/src/commands/runExperimentRunCommand.js +90 -3
- package/dist/src/evaluation/runMyDevKitRetrieval.js +1 -0
- package/dist/src/experiments/defaultRegistry.js +2 -0
- package/dist/src/experiments/plugins/contextPackGeneration/analysis.js +157 -0
- package/dist/src/experiments/plugins/contextPackGeneration/analysisArtifact.js +22 -0
- package/dist/src/experiments/plugins/contextPackGeneration/analysisTypes.js +1 -0
- package/dist/src/experiments/plugins/contextPackGeneration/buildContextPack.js +84 -0
- package/dist/src/experiments/plugins/contextPackGeneration/config.js +39 -0
- package/dist/src/experiments/plugins/contextPackGeneration/execution.js +361 -0
- package/dist/src/experiments/plugins/contextPackGeneration/executionArtifact.js +17 -0
- package/dist/src/experiments/plugins/contextPackGeneration/executionTypes.js +11 -0
- package/dist/src/experiments/plugins/contextPackGeneration/identityEvidence.js +71 -0
- package/dist/src/experiments/plugins/contextPackGeneration/index.js +19 -0
- package/dist/src/experiments/plugins/contextPackGeneration/localSubjectExecution.js +146 -0
- package/dist/src/experiments/plugins/contextPackGeneration/localSubjectPrivacy.js +276 -0
- package/dist/src/experiments/plugins/contextPackGeneration/metadata.js +24 -0
- package/dist/src/experiments/plugins/contextPackGeneration/metrics.js +46 -0
- package/dist/src/experiments/plugins/contextPackGeneration/packArtifact.js +17 -0
- package/dist/src/experiments/plugins/contextPackGeneration/packEvidence.js +152 -0
- package/dist/src/experiments/plugins/contextPackGeneration/packSelectionPolicy.js +305 -0
- package/dist/src/experiments/plugins/contextPackGeneration/plugin.js +355 -0
- package/dist/src/experiments/plugins/contextPackGeneration/renderContextPack.js +61 -0
- package/dist/src/experiments/plugins/contextPackGeneration/types.js +29 -0
- package/dist/src/report/experiments/buildContextPackGenerationReport.js +171 -0
- package/dist/src/report/experiments/buildPluginExperimentReport.js +24 -2
- package/dist/src/report/experiments/contextPackGenerationReportModel.js +9 -0
- package/dist/src/report/experiments/index.js +5 -0
- package/dist/src/report/experiments/loadContextPackArtifacts.js +43 -0
- package/dist/src/report/experiments/renderContextPackGenerationHtml.js +122 -0
- package/dist/src/report/experiments/renderContextPackGenerationText.js +101 -0
- package/dist/src/report/experiments/renderPluginExperimentReportHtml.js +3 -0
- package/dist/src/report/experiments/renderPluginExperimentReportText.js +7 -0
- package/dist/src/report/experiments/renderRetrievalQueryStrategyComparisonHtml.js +15 -15
- package/dist/src/report/experiments/writePluginExperimentReports.js +2 -0
- package/docs/METRICS.md +28 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,7 +4,16 @@ All notable changes to my-dev-kit-lab are documented here.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
-
## [0.8.
|
|
7
|
+
## [0.8.2] - 2026-10-07
|
|
8
|
+
|
|
9
|
+
### Context-pack generation experiments
|
|
10
|
+
|
|
11
|
+
- Added the experimental `context-pack-generation` plugin, which always compares the fixed `raw-full-file` and `context-pack` treatments using reproducible, bounded task-specific context packs.
|
|
12
|
+
- Reused retrieval-quality coverage metrics and estimated context-size semantics, with paired token savings, percent savings, and objective deltas; reports include bounded JSON, text, and HTML pack previews without a composite winner or ranking.
|
|
13
|
+
- Added external-local execution that calculates science on real identities before privacy projection, persists redacted reports, and writes no durable context-pack body.
|
|
14
|
+
- Extended installed-package acceptance and verified compatibility with the published `@dailephd/my-dev-kit@1.12.5` package.
|
|
15
|
+
|
|
16
|
+
## [0.8.1] - 2026-10-06
|
|
8
17
|
|
|
9
18
|
### Retrieval query strategy comparison
|
|
10
19
|
|
package/README.md
CHANGED
|
@@ -6,9 +6,9 @@ my-dev-kit provides local repository indexing and graph-guided retrieval. my-dev
|
|
|
6
6
|
|
|
7
7
|
Whole-ecosystem workflows are centralized in [my-dev-kit/docs/ECOSYSTEM_DEVELOPMENT_WORKFLOWS.md](https://github.com/dailephd/my-dev-kit/blob/main/docs/ECOSYSTEM_DEVELOPMENT_WORKFLOWS.md). Its [command-surface compatibility map](https://github.com/dailephd/my-dev-kit/blob/main/docs/ECOSYSTEM_DEVELOPMENT_WORKFLOWS.md#915-command-surface-compatibility-map) documents which Lab outputs can safely seed my-dev-kit investigation, Observer reference work, or Orchestrator evidence, and which look-alike artifacts are not direct inputs.
|
|
8
8
|
|
|
9
|
-
The latest release is v0.8.
|
|
9
|
+
The latest published release is v0.8.2 (context-pack generation experiments), and the repository package version is 0.8.2. The previous published release is v0.8.1 (retrieval query strategy comparison). The v0.4.5 context-integrity evaluation remains intentionally frozen against the published `@dailephd/my-dev-kit@1.10.4` and `@dailephd/my-dev-kit-orchestrator@1.2.3` contracts; those versions are historical validation baselines, not a statement that they are the ecosystem's current releases. See [docs/CURRENT_STATE.md](docs/CURRENT_STATE.md) for current state.
|
|
10
10
|
|
|
11
|
-
v0.8.2 (
|
|
11
|
+
The next planned milestone after v0.8.2 is v0.9.0 (agent-success-rate experiments).
|
|
12
12
|
|
|
13
13
|
v0.4.6 adds a supported `my-dev-kit-lab` installed CLI router (`--help`, `--version`, `security validate`, `audit`, the `experiment` family, `report render`, `plots generate`, `gallery build`, `demo final`, and the historical direct final-demo invocation form), a writable lab workspace model kept separate from the installed package and the inspected target, and a permanent packed-tarball installation/execution acceptance gate (`npm run verify:packed-package`). See [docs/ROADMAP.md](docs/ROADMAP.md) and [docs/CURRENT_STATE.md](docs/CURRENT_STATE.md) for status detail and [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for the runtime path model. The "Installed CLI" section below documents the shipped command surface; the source-checkout `npm run` workflow in Quickstart remains available for contributors.
|
|
14
14
|
|
|
@@ -24,6 +24,7 @@ v0.4.6 adds a supported `my-dev-kit-lab` installed CLI router (`--help`, `--vers
|
|
|
24
24
|
- **Run context-window scaling over a local Git repository (v0.7.2, released):** `context-window-scaling` can also run against an explicitly selected local Git repository with `--target <repository> --local-subject-config <path>` (a `LocalRepositorySubjectConfigV1` JSON file). The Lab treats the repository as read-only and compares its state before and after the run, builds contexts only from files Git does not ignore (ignored, oversized (over 1 MiB), symbolic-link, and other unsafe entries are excluded), requires the output directory to be outside the repository, and keeps the repository path, source text, and file names out of durable artifacts (file identities appear as numbered `<redacted file N>` placeholders). A `local-repository-subject-manifest.json` records the logical subject ID, the full Git commit, the branch, and aggregate size metadata. This measures operational context fit only, not retrieval precision, recall, or ranking quality. It is part of the current package. See [docs/COMMANDS.md](docs/COMMANDS.md) and [docs/WORKFLOWS.md](docs/WORKFLOWS.md).
|
|
25
25
|
- **Measure retrieval precision and recall (v0.8.0, released):** the `retrieval-precision-recall` plugin runs the existing my-dev-kit search, lookup, slice, and source lifecycle for each case and compares the retrieved files and symbols with a deterministic answer key, without invoking any agent. It reports file precision and recall, symbol precision and recall, fact coverage (from explicit fact-to-context mappings), irrelevant context ratio, the estimated retrieved token count, and the missed and irrelevant identities, as `retrieval-precision-recall-execution.json` plus `report.json`, `report.html`, and `report.txt`. By default it runs a bundled 12-case corpus over two benchmark projects against the Lab itself. It can also run over an explicitly selected local Git repository with `--target <repository> --local-subject-config <path>`; in that mode the repository is treated as read-only and durable outputs redact the repository path and file, symbol, and fact identities while keeping the numeric evidence. These are set-based measures with no ranking, winner, or composite score. See [docs/COMMANDS.md](docs/COMMANDS.md), [docs/WORKFLOWS.md](docs/WORKFLOWS.md), and [docs/METRICS.md](docs/METRICS.md).
|
|
26
26
|
- **Compare retrieval query strategies (v0.8.1, released):** the `retrieval-query-strategy-comparison` plugin compares seven fixed my-dev-kit retrieval strategies without running any coding agent: `keyword-search`, `symbol-lookup`, `graph-neighborhood`, `source-slice`, `data-model-graph`, `model-view-lineage`, and `combined-graph-guided`. In bundled mode it uses the same frozen 12-case warm-index benchmark corpus as `retrieval-precision-recall`, and every selected case receives all seven strategies (filters narrow cases or projects, never strategies). File and symbol quality keeps the inherited v0.8.0 set-based semantics and adds balanced file and symbol F1 (`2TP / (2TP + FP + FN)`); fact coverage and the estimated retrieved token count complete the four comparison objectives. Strategies are compared over matched complete cases only, as macro means for four scopes (`overall`, `localized`, `cross-module`, `broad-change`, reusing each case's existing task-locality label), using Pareto dominance on mean file F1, mean symbol F1, mean fact coverage (all maximized) and mean retrieved tokens (minimized). A best strategy is named only when the Pareto front has a single member; when several strategies remain nondominated the report states a tradeoff. There is no scalar or composite score and no ordinal ranking. The plugin can also run over an explicitly selected local Git repository through the existing `LocalRepositorySubject` boundary with `--target <repository> --local-subject-config <path>`; the repository stays read-only and durable output redacts private file, symbol, fact, semantic-node, warning, and case-title identities while preserving the numeric results. It writes `retrieval-query-strategy-comparison-execution.json`, `retrieval-query-strategy-comparison-analysis.json`, and `report.json`, `report.html`, and `report.txt`. No context-pack behavior is included. See [docs/COMMANDS.md](docs/COMMANDS.md), [docs/WORKFLOWS.md](docs/WORKFLOWS.md), and [docs/METRICS.md](docs/METRICS.md#retrieval-query-strategy-comparison-metrics-v081).
|
|
27
|
+
- **Generate auditable task context packs (v0.8.2):** `context-pack-generation` runs both fixed treatments, `raw-full-file` and `context-pack`, for each selected case. A pack contains a task summary, relevant files and symbols, bounded source slices, call relationships, tests, and evidence notes. Analysis reuses retrieval fact coverage and file/symbol precision, recall, and F1, reports estimated context size and paired raw-versus-pack tokens saved, percent saved, and objective deltas, and names no composite winner or ranking. Bundled runs persist pack artifacts and reports include a bounded preview. External-local runs calculate science on real identities, redact before persistence, omit durable pack bodies, and show redacted report previews. See [docs/COMMANDS.md](docs/COMMANDS.md), [docs/WORKFLOWS.md](docs/WORKFLOWS.md), and [docs/METRICS.md](docs/METRICS.md).
|
|
27
28
|
- **Assess affected-neighborhood evidence (v0.6.1, released; retained in the current package):** using the baseline my-dev-kit graph of the same prepared warm index (loaded once per session), each task's confirmed modified or missing indexed files, and the baseline symbols those files contain, are mapped to graph nodes. The affected neighborhood is those seed nodes plus their direct one-hop neighbors (edges of any kind, either direction). The task's `expectedFiles` and file-scoped `expectedSymbols` are mapped to graph nodes and compared with that neighborhood, which yields six numeric warm-side metrics (`changedFileCount`, `changedSymbolCount`, `affectedNodeCount`, `affectedEdgeCount`, `taskOverlapCount`, `taskOverlapPercent`) plus categorical evidence: a `related`, `unrelated`, or `unknown` relationship and a `recommended`, `not-indicated`, or `unknown` `reindexRecommendation`. Incomplete evidence is reported as `unknown`, never as unrelated. The assessment is recorded additively in `warm-index-execution.json` and shown in the existing `report.json`/`report.txt`/`report.html` warm-index reports. It is observational only: retrieval, execution status, and provider status are unchanged, nothing is reindexed automatically, and no CLI flag, experiment plugin, plot, or gallery item was added. See [docs/METRICS.md](docs/METRICS.md#affected-neighborhood-metrics-v061) and [docs/WORKFLOWS.md](docs/WORKFLOWS.md#warm-index-reuse-experiment).
|
|
28
29
|
- **Audit repository health:** run conservative code-rot detectors for TypeScript/JavaScript, Python, Java, and Kotlin, or adapt security findings into the common audit report.
|
|
29
30
|
- **Validate CLI/package security:** inspect dependencies, package contents, path and subprocess boundaries, malformed inputs, optional static scanners, and bounded fuzz targets.
|
|
@@ -243,9 +244,9 @@ See [docs/METRICS.md](docs/METRICS.md) for full metric definitions.
|
|
|
243
244
|
- Claude and Codex token totals are used only when the CLI/run exposes them; missing provider token evidence stays explicitly unavailable (never zero, and never substituted with an estimated context-token count)
|
|
244
245
|
- Codex and Claude campaign runs can produce timeouts, invalid-output, agent-unavailable, or agent-limit-reached outcomes; these are structured provider states, not warm-index infrastructure failures
|
|
245
246
|
- Small projects may make raw-full-file cheaper than my-dev-kit-guided; larger localized tasks are where my-dev-kit is expected to become more useful
|
|
246
|
-
- The generic experiment-plugin framework has
|
|
247
|
+
- The generic experiment-plugin framework has seven registered plugins: `context-strategy-comparison`, `warm-index-reuse`, `incremental-change-staleness`, `context-window-scaling`, `retrieval-precision-recall`, `retrieval-query-strategy-comparison`, and `context-pack-generation`. The v0.7.0 context-window-scaling plugin is released; v0.7.1 added caller-supplied synthetic repositories; v0.7.2 added an explicitly selected local Git repository. The context-pack-generation plugin shipped in v0.8.2; agent-success plugins remain planned.
|
|
247
248
|
- `retrieval-precision-recall` (v0.8.0) measures set-based retrieval quality, not ranked retrieval: it has no ranking metrics, no retrieval-strategy comparison of its own (the separate `retrieval-query-strategy-comparison` plugin added in v0.8.1 compares strategies), no winner or composite score, and no real agents. Its lifecycle expands only the top search candidate. An external-local run fails as a whole if any case fails a safety or execution check, and then writes no normal artifact or report family. Durable external-local identities are redacted, although the console summary of a successful run can still show the physical output directory. Platform path-length limits can make a run with very long paths fail cleanly instead of executing.
|
|
248
|
-
- `retrieval-query-strategy-comparison` (v0.8.1) runs no coding agent and does not evaluate answer quality. Its retrieved token count is a context-size estimate, not provider billing telemetry. Semantic strategies (`data-model-graph`, `model-view-lineage`) can truthfully be partial or unavailable for a case, and a case is then excluded from the matched cross-strategy comparison for every strategy while its per-strategy evidence is kept. The Pareto front is a set of nondominated strategies in canonical strategy order, not an ordinal ranking. The plugin reports no significance test or confidence interval
|
|
249
|
+
- `retrieval-query-strategy-comparison` (v0.8.1) runs no coding agent and does not evaluate answer quality. Its retrieved token count is a context-size estimate, not provider billing telemetry. Semantic strategies (`data-model-graph`, `model-view-lineage`) can truthfully be partial or unavailable for a case, and a case is then excluded from the matched cross-strategy comparison for every strategy while its per-strategy evidence is kept. The Pareto front is a set of nondominated strategies in canonical strategy order, not an ordinal ranking. The plugin reports no significance test or confidence interval and selects no strategy automatically. External-local mode rejects `--case` and `--benchmark-project`, builds one private base index per configured case, and fails as a whole, writing no normal artifact or report family, if any safety or privacy check fails.
|
|
249
250
|
- `warm-index-reuse` without `--campaign` still uses the deterministic fake agent only. With `--campaign`, exactly one real provider (Codex or Claude) runs per campaign preset — there is no multi-agent matrix, retry, provider switching, or scheduler. The v0.5.1 expanded corpus is deterministic fake-agent benchmark evidence when run without `--campaign`. No warm-index mode calculates a token-savings percentage, break-even task, winner, or ranking, and component-duration sums are not wall-clock latency
|
|
250
251
|
- The current release does not guarantee token savings; it produces auditable evidence for specific cases, targets, agents, and strategies
|
|
251
252
|
- Context-window scaling uses estimated tokens (`ceil(characters / 4)`) and experiment budgets, not actual provider tokenizer or model-window limits. Its deterministic fake-agent correctness check does not measure semantic sensitivity to the supplied context.
|
package/dist/src/cli/help.js
CHANGED
|
@@ -156,7 +156,7 @@ export function renderExperimentRunHelp() {
|
|
|
156
156
|
" --case <ids> Comma-separated case ids to run",
|
|
157
157
|
" --benchmark-project <ids> Comma-separated benchmark project ids to run",
|
|
158
158
|
"",
|
|
159
|
-
"my-dev-kit command override (warm-index-reuse, incremental-change-staleness, context-window-scaling, retrieval-precision-recall,
|
|
159
|
+
"my-dev-kit command override (warm-index-reuse, incremental-change-staleness, context-window-scaling, retrieval-precision-recall, retrieval-query-strategy-comparison, and context-pack-generation):",
|
|
160
160
|
" --kit-command <command> my-dev-kit command used by the selected experiment plugin",
|
|
161
161
|
" (defaults: warm-index-reuse and context-window-scaling use",
|
|
162
162
|
" npx @dailephd/my-dev-kit@latest;",
|
|
@@ -219,6 +219,21 @@ export function renderExperimentRunHelp() {
|
|
|
219
219
|
" strategies are reported as a tradeoff. No scalar score or total",
|
|
220
220
|
" ranking exists.",
|
|
221
221
|
"",
|
|
222
|
+
"context-pack-generation only:",
|
|
223
|
+
" Bundled/self mode runs the frozen 12-case corpus and accepts only",
|
|
224
|
+
" --out, --case, --benchmark-project and --kit-command (default",
|
|
225
|
+
" npx @dailephd/my-dev-kit@latest); --case and --benchmark-project",
|
|
226
|
+
" narrow the case set. Both treatments, raw-full-file and",
|
|
227
|
+
" context-pack, always run for every case; there is no treatment,",
|
|
228
|
+
" strategy, or selection-policy option. External-local mode requires",
|
|
229
|
+
" --target together with --local-subject-config and accepts only",
|
|
230
|
+
" --out, --target, --local-subject-config and --kit-command; --case",
|
|
231
|
+
" and --benchmark-project are rejected because the local-subject",
|
|
232
|
+
" config owns the case set. The repository is never modified and",
|
|
233
|
+
" durable output withholds private file, symbol, fact, semantic-node,",
|
|
234
|
+
" call-relationship, test, source-text, warning, task and case-title",
|
|
235
|
+
" identities; no context-pack body is written for an external run.",
|
|
236
|
+
"",
|
|
222
237
|
"warm-index-reuse only:",
|
|
223
238
|
" Legacy/default mode uses the deterministic fake agent only;",
|
|
224
239
|
" agent options below are not accepted by this plugin.",
|
|
@@ -139,6 +139,9 @@ function describeTargetBehavior(plugin) {
|
|
|
139
139
|
if (plugin.metadata.id === "retrieval-query-strategy-comparison") {
|
|
140
140
|
return "Two subject modes. Bundled (default): the frozen 12-case warm-index corpus compares all seven retrieval query strategies and may be narrowed with --case and --benchmark-project. External local repository: an explicitly selected local Git worktree via --target <path> together with --local-subject-config <path>; the local-subject config owns the case set, one private base index is built per configured case, core strategies share that base, semantic strategies use isolated copies, the repository is never modified, and durable output withholds private file, symbol, fact, semantic-node, warning and case-title identities while preserving numeric scientific results. Bundled filters are not accepted in external-local mode.";
|
|
141
141
|
}
|
|
142
|
+
if (plugin.metadata.id === "context-pack-generation") {
|
|
143
|
+
return "Two subject modes. Bundled (default): the frozen 12-case warm-index corpus against the Lab itself, narrowable with --case and --benchmark-project. External local repository: an explicitly selected local Git worktree via --target <path> together with --local-subject-config <path>; the local-subject config owns the case set, the repository is never modified, the output directory must be outside it, one private base index is built per configured case, and durable output withholds private file, symbol, fact, semantic-node, call-relationship, test, source-text, warning, task and case-title identities while preserving numeric scientific results; no context-pack body is written externally. Bundled filters are not accepted in external-local mode. Both treatments (raw-full-file and context-pack) always run for every case; there is no treatment, strategy, or selection-policy option.";
|
|
144
|
+
}
|
|
142
145
|
if (supportsExternal) {
|
|
143
146
|
return "Runs against the current lab repository when --target is omitted, or against an explicit local target project when --target <path> is provided. Outputs stay under lab-controlled output directories by default.";
|
|
144
147
|
}
|
|
@@ -165,7 +168,8 @@ function describeExamples(plugin) {
|
|
|
165
168
|
if (plugin.metadata.supportedTargets.includes("external-local") &&
|
|
166
169
|
plugin.metadata.id !== "context-window-scaling" &&
|
|
167
170
|
plugin.metadata.id !== "retrieval-precision-recall" &&
|
|
168
|
-
plugin.metadata.id !== "retrieval-query-strategy-comparison"
|
|
171
|
+
plugin.metadata.id !== "retrieval-query-strategy-comparison" &&
|
|
172
|
+
plugin.metadata.id !== "context-pack-generation") {
|
|
169
173
|
examples.push(`my-dev-kit-lab experiment run --experiment ${plugin.metadata.id} --target "Z:\\Users\\newuser\\Projects\\my-dev-kit-v1"${agentOptions}${screenshotOption}`);
|
|
170
174
|
}
|
|
171
175
|
// Only advertise campaign examples for plugins that declare the campaignPreset config field.
|
|
@@ -178,6 +182,9 @@ function describeExamples(plugin) {
|
|
|
178
182
|
if (plugin.metadata.id === "retrieval-query-strategy-comparison") {
|
|
179
183
|
examples.push(`my-dev-kit-lab experiment run --experiment ${plugin.metadata.id} --case <case-id> --out <run-dir>`, `my-dev-kit-lab experiment run --experiment ${plugin.metadata.id} --target <local-git-repository> --local-subject-config <path-to-local-subject-config.json> --out <run-dir-outside-the-repository>`);
|
|
180
184
|
}
|
|
185
|
+
if (plugin.metadata.id === "context-pack-generation") {
|
|
186
|
+
examples.push(`my-dev-kit-lab experiment run --experiment ${plugin.metadata.id} --case <case-id> --out <run-dir>`, `my-dev-kit-lab experiment run --experiment ${plugin.metadata.id} --target <local-git-repository> --local-subject-config <path-to-local-subject-config.json> --out <run-dir-outside-the-repository>`);
|
|
187
|
+
}
|
|
181
188
|
if (plugin.metadata.id === "context-window-scaling") {
|
|
182
189
|
examples.push(`my-dev-kit-lab experiment run --experiment ${plugin.metadata.id} --synthetic-config <path-to-config.json> --out <run-dir>`, `my-dev-kit-lab experiment run --experiment ${plugin.metadata.id} --target <local-git-repository> --local-subject-config <path-to-local-subject-config.json> --out <run-dir-outside-the-repository>`);
|
|
183
190
|
}
|
|
@@ -6,6 +6,7 @@ import { readBenchmarkProjectProfiles, readEvaluationCases } from "../evaluation
|
|
|
6
6
|
import { summarizeRetrievalGroundTruthIssuesSafely, validateRetrievalPrecisionRecallCorpus } from "../evaluation/retrievalQuality/index.js";
|
|
7
7
|
import { loadLocalRepositorySubject } from "../evaluation/localRepositorySubject/index.js";
|
|
8
8
|
import { contextStrategyComparisonPlugin, contextWindowScalingPlugin, createDefaultExperimentPluginRegistry, getWarmIndexCampaignPreset, incrementalChangeStalenessPlugin, parseContextBudgetsCliValue, parseWarmIndexCampaignPresetId, resolveExperimentTarget, retrievalPrecisionRecallPlugin, retrievalQueryStrategyComparisonPlugin, runExperiment, warmIndexReusePlugin } from "../experiments/index.js";
|
|
9
|
+
import { contextPackGenerationPlugin } from "../experiments/plugins/contextPackGeneration/index.js";
|
|
9
10
|
import { prepareSyntheticContextWindowScalingInputs } from "../experiments/plugins/contextWindowScaling/index.js";
|
|
10
11
|
import { projectExternalLocalTarget, projectRunForExternalLocalPersistence, } from "../experiments/plugins/contextWindowScaling/localSubjectPrivacy.js";
|
|
11
12
|
import { assertWorkRootOutsideTarget } from "../experiments/plugins/contextWindowScaling/localSubjectScratch.js";
|
|
@@ -64,6 +65,12 @@ const RETRIEVAL_PRECISION_RECALL_EXTERNAL_ALLOWED_FLAGS = ["--experiment", "--ou
|
|
|
64
65
|
const RETRIEVAL_QUERY_STRATEGY_COMPARISON_CASES_RESOURCE = "benchmarks/contracts/warm-index-benchmark-cases.json";
|
|
65
66
|
const RETRIEVAL_QUERY_STRATEGY_COMPARISON_ALLOWED_FLAGS = ["--experiment", "--out", "--case", "--benchmark-project", "--kit-command"];
|
|
66
67
|
const RETRIEVAL_QUERY_STRATEGY_COMPARISON_EXTERNAL_ALLOWED_FLAGS = ["--experiment", "--out", "--target", "--local-subject-config", "--kit-command"];
|
|
68
|
+
// context-pack-generation (v0.8.2) reuses the same frozen bundled corpus. Both treatments (raw-full-file and context-pack)
|
|
69
|
+
// always run, so there is no treatment or strategy option. External-local mode (--target with --local-subject-config) is a
|
|
70
|
+
// different subject source: the local-subject config owns the case set, so the bundled corpus filters are not accepted there.
|
|
71
|
+
const CONTEXT_PACK_GENERATION_CASES_RESOURCE = "benchmarks/contracts/warm-index-benchmark-cases.json";
|
|
72
|
+
const CONTEXT_PACK_GENERATION_ALLOWED_FLAGS = ["--experiment", "--out", "--case", "--benchmark-project", "--kit-command"];
|
|
73
|
+
const CONTEXT_PACK_GENERATION_EXTERNAL_ALLOWED_FLAGS = ["--experiment", "--out", "--target", "--local-subject-config", "--kit-command"];
|
|
67
74
|
export async function runExperimentRunCommandFromArgs(argv, options = {}) {
|
|
68
75
|
try {
|
|
69
76
|
const args = parseRunExperimentArgs(argv);
|
|
@@ -315,7 +322,8 @@ export function parseRunExperimentArgs(argv) {
|
|
|
315
322
|
incrementalChangeStalenessPlugin.metadata.id,
|
|
316
323
|
contextWindowScalingPlugin.metadata.id,
|
|
317
324
|
retrievalPrecisionRecallPlugin.metadata.id,
|
|
318
|
-
retrievalQueryStrategyComparisonPlugin.metadata.id
|
|
325
|
+
retrievalQueryStrategyComparisonPlugin.metadata.id,
|
|
326
|
+
contextPackGenerationPlugin.metadata.id
|
|
319
327
|
];
|
|
320
328
|
if (experimentId === retrievalPrecisionRecallPlugin.metadata.id) {
|
|
321
329
|
// Mode matrix, no precedence rules: neither flag = bundled; both = external-local; exactly one is rejected.
|
|
@@ -367,6 +375,30 @@ export function parseRunExperimentArgs(argv) {
|
|
|
367
375
|
throw new Error("--benchmark-project must list at least one benchmark project id.");
|
|
368
376
|
}
|
|
369
377
|
}
|
|
378
|
+
if (experimentId === contextPackGenerationPlugin.metadata.id) {
|
|
379
|
+
// Mode matrix, no precedence rules: neither flag = bundled; both = external-local; exactly one is rejected.
|
|
380
|
+
if (targetPath !== undefined && localSubjectConfigPath === undefined) {
|
|
381
|
+
throw new Error(`External ${experimentId} targets require --local-subject-config.`);
|
|
382
|
+
}
|
|
383
|
+
if (localSubjectConfigPath !== undefined && targetPath === undefined) {
|
|
384
|
+
throw new Error(`--local-subject-config requires an external --target for ${experimentId}.`);
|
|
385
|
+
}
|
|
386
|
+
const externalMode = targetPath !== undefined;
|
|
387
|
+
if (externalMode && (seenFlags.includes("--case") || seenFlags.includes("--benchmark-project"))) {
|
|
388
|
+
throw new Error("--case and --benchmark-project cannot be combined with --local-subject-config; the local subject config owns the case set.");
|
|
389
|
+
}
|
|
390
|
+
const allowedFlags = externalMode ? CONTEXT_PACK_GENERATION_EXTERNAL_ALLOWED_FLAGS : CONTEXT_PACK_GENERATION_ALLOWED_FLAGS;
|
|
391
|
+
const unsupported = [...new Set(seenFlags.filter((flag) => !allowedFlags.includes(flag)))];
|
|
392
|
+
if (unsupported.length > 0) {
|
|
393
|
+
throw new Error(`${unsupported.join(", ")} ${unsupported.length === 1 ? "is" : "are"} not supported for --experiment ${experimentId}${externalMode ? " in external-local mode" : ""}; supported options: ${allowedFlags.join(", ")}.`);
|
|
394
|
+
}
|
|
395
|
+
if (seenFlags.includes("--case") && caseIds.length === 0) {
|
|
396
|
+
throw new Error("--case must list at least one case id.");
|
|
397
|
+
}
|
|
398
|
+
if (seenFlags.includes("--benchmark-project") && benchmarkProjects.length === 0) {
|
|
399
|
+
throw new Error("--benchmark-project must list at least one benchmark project id.");
|
|
400
|
+
}
|
|
401
|
+
}
|
|
370
402
|
if (contextBudgets !== undefined && experimentId !== contextWindowScalingPlugin.metadata.id) {
|
|
371
403
|
throw new Error(`--context-budgets is only supported for --experiment ${contextWindowScalingPlugin.metadata.id}.`);
|
|
372
404
|
}
|
|
@@ -376,8 +408,9 @@ export function parseRunExperimentArgs(argv) {
|
|
|
376
408
|
if (localSubjectConfigPath !== undefined &&
|
|
377
409
|
experimentId !== contextWindowScalingPlugin.metadata.id &&
|
|
378
410
|
experimentId !== retrievalPrecisionRecallPlugin.metadata.id &&
|
|
379
|
-
experimentId !== retrievalQueryStrategyComparisonPlugin.metadata.id
|
|
380
|
-
|
|
411
|
+
experimentId !== retrievalQueryStrategyComparisonPlugin.metadata.id &&
|
|
412
|
+
experimentId !== contextPackGenerationPlugin.metadata.id) {
|
|
413
|
+
throw new Error(`--local-subject-config is only supported for --experiment ${contextWindowScalingPlugin.metadata.id}, ${retrievalPrecisionRecallPlugin.metadata.id}, ${retrievalQueryStrategyComparisonPlugin.metadata.id}, or ${contextPackGenerationPlugin.metadata.id}.`);
|
|
381
414
|
}
|
|
382
415
|
if (experimentId === contextWindowScalingPlugin.metadata.id) {
|
|
383
416
|
const unsupported = [...new Set(seenFlags.filter((flag) => !CONTEXT_WINDOW_SCALING_ALLOWED_FLAGS.includes(flag)))];
|
|
@@ -499,6 +532,11 @@ async function loadPluginInputs(args, toolRoot, context, outputRoot) {
|
|
|
499
532
|
? loadRetrievalPrecisionRecallLocalSubjectInputs(args, toolRoot, context, outputRoot)
|
|
500
533
|
: loadRetrievalPrecisionRecallInputs(args, toolRoot, context);
|
|
501
534
|
}
|
|
535
|
+
if (args.experimentId === contextPackGenerationPlugin.metadata.id) {
|
|
536
|
+
return args.localSubjectConfigPath !== undefined
|
|
537
|
+
? loadContextPackGenerationLocalSubjectInputs(args, toolRoot, context, outputRoot)
|
|
538
|
+
: loadContextPackGenerationInputs(args, toolRoot, context);
|
|
539
|
+
}
|
|
502
540
|
if (args.experimentId === retrievalQueryStrategyComparisonPlugin.metadata.id) {
|
|
503
541
|
return args.localSubjectConfigPath !== undefined
|
|
504
542
|
? loadRetrievalQueryStrategyComparisonLocalSubjectInputs(args, toolRoot, context, outputRoot)
|
|
@@ -506,6 +544,55 @@ async function loadPluginInputs(args, toolRoot, context, outputRoot) {
|
|
|
506
544
|
}
|
|
507
545
|
return undefined;
|
|
508
546
|
}
|
|
547
|
+
// context-pack-generation (bundled/self): the frozen warm-index corpus through the established resource resolver, profile
|
|
548
|
+
// and corpus validation. The plugin applies --case/--benchmark-project selection and owns execution, science and artifacts.
|
|
549
|
+
async function loadContextPackGenerationInputs(args, toolRoot, context) {
|
|
550
|
+
const validation = contextPackGenerationPlugin.validateConfig(args.config);
|
|
551
|
+
if (!validation.valid || !validation.config) {
|
|
552
|
+
throw new Error(`Invalid context pack generation config: ${validation.errors.join("; ")}`);
|
|
553
|
+
}
|
|
554
|
+
const projectProfiles = await readBenchmarkProjectProfiles(resolvePackageResource(context, DEFAULT_PROJECT_PROFILES_RESOURCE), toolRoot);
|
|
555
|
+
const cases = await readEvaluationCases(resolvePackageResource(context, CONTEXT_PACK_GENERATION_CASES_RESOURCE), toolRoot, {
|
|
556
|
+
projectProfiles,
|
|
557
|
+
requireProjectProfileRef: true
|
|
558
|
+
});
|
|
559
|
+
const corpusErrors = validateRetrievalPrecisionRecallCorpus(cases);
|
|
560
|
+
if (corpusErrors.length > 0) {
|
|
561
|
+
throw new Error(`Invalid context-pack-generation corpus: ${corpusErrors.join(" ")}`);
|
|
562
|
+
}
|
|
563
|
+
return { cases };
|
|
564
|
+
}
|
|
565
|
+
// External-local context-pack-generation: same loader and safety order as the other external-local retrieval experiments,
|
|
566
|
+
// with the plugin's own config validation. Ground-truth issues carry logical case ids and counts only.
|
|
567
|
+
async function loadContextPackGenerationLocalSubjectInputs(args, toolRoot, context, outputRoot) {
|
|
568
|
+
const validation = contextPackGenerationPlugin.validateConfig(args.config);
|
|
569
|
+
if (!validation.valid || !validation.config) {
|
|
570
|
+
throw new Error(`Invalid context pack generation config: ${validation.errors.join("; ")}`);
|
|
571
|
+
}
|
|
572
|
+
if (outputRoot === undefined || args.localSubjectConfigPath === undefined) {
|
|
573
|
+
throw new Error("Internal error: external-local runs require a resolved experiment output root and config path.");
|
|
574
|
+
}
|
|
575
|
+
const target = resolveExperimentTarget(args.targetPath, toolRoot);
|
|
576
|
+
if (target.isSelf || target.kind !== "external-local") {
|
|
577
|
+
throw new Error("Local-repository subject mode requires an external-local target; --target must not be the Lab repository.");
|
|
578
|
+
}
|
|
579
|
+
try {
|
|
580
|
+
await assertWorkRootOutsideTarget(outputRoot, target.targetRoot);
|
|
581
|
+
}
|
|
582
|
+
catch {
|
|
583
|
+
throw new Error("Experiment output root must not be inside the external target project.");
|
|
584
|
+
}
|
|
585
|
+
const configPath = path.resolve(context.invocationCwd, args.localSubjectConfigPath);
|
|
586
|
+
const localSubjectConfig = await readJsonConfigFile("--local-subject-config", configPath, args.localSubjectConfigPath);
|
|
587
|
+
const localSubject = await loadLocalRepositorySubject({ config: localSubjectConfig, repositoryPath: target.targetRoot });
|
|
588
|
+
const groundTruthIssues = summarizeRetrievalGroundTruthIssuesSafely(localSubject.evaluationCases);
|
|
589
|
+
if (groundTruthIssues.length > 0) {
|
|
590
|
+
throw new Error(`Invalid context-pack-generation ground truth for the local subject (${groundTruthIssues
|
|
591
|
+
.map((issue) => `case ${issue.caseId}: ${issue.issueCount} issue${issue.issueCount === 1 ? "" : "s"}`)
|
|
592
|
+
.join("; ")}); details withheld.`);
|
|
593
|
+
}
|
|
594
|
+
return { cases: localSubject.evaluationCases, localSubject, env: process.env };
|
|
595
|
+
}
|
|
509
596
|
// External-local retrieval-query-strategy-comparison: same loader and safety order as retrieval-precision-recall, with
|
|
510
597
|
// the plugin's own config validation. Ground-truth issues carry logical case ids and counts only.
|
|
511
598
|
async function loadRetrievalQueryStrategyComparisonLocalSubjectInputs(args, toolRoot, context, outputRoot) {
|
|
@@ -183,6 +183,7 @@ export async function buildMyDevKitIndex(options) {
|
|
|
183
183
|
...(options.excludePaths ?? []).flatMap((excludePath) => ["--exclude", excludePath]),
|
|
184
184
|
"--out",
|
|
185
185
|
options.indexDir,
|
|
186
|
+
...(options.callGraph === true ? ["--call-graph"] : []),
|
|
186
187
|
...(mode.kind === "incremental" ? ["--incremental", "--refresh-scope", mode.refreshScope] : []),
|
|
187
188
|
"--json"
|
|
188
189
|
]
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ExperimentPluginRegistry } from "./registry.js";
|
|
2
2
|
import { contextStrategyComparisonPlugin } from "./plugins/contextStrategyComparison/index.js";
|
|
3
|
+
import { contextPackGenerationPlugin } from "./plugins/contextPackGeneration/index.js";
|
|
3
4
|
import { contextWindowScalingPlugin } from "./plugins/contextWindowScaling/index.js";
|
|
4
5
|
import { incrementalChangeStalenessPlugin } from "./plugins/incrementalChangeStaleness/index.js";
|
|
5
6
|
import { retrievalPrecisionRecallPlugin } from "./plugins/retrievalPrecisionRecall/index.js";
|
|
@@ -13,5 +14,6 @@ export function createDefaultExperimentPluginRegistry() {
|
|
|
13
14
|
registry.register(contextWindowScalingPlugin);
|
|
14
15
|
registry.register(retrievalPrecisionRecallPlugin);
|
|
15
16
|
registry.register(retrievalQueryStrategyComparisonPlugin);
|
|
17
|
+
registry.register(contextPackGenerationPlugin);
|
|
16
18
|
return registry;
|
|
17
19
|
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { calculateRetrievalQualityMetricsFromIdentityEvidence } from "../../../evaluation/retrievalQuality/metrics.js";
|
|
2
|
+
import { calculateBalancedIdentityF1 } from "../retrievalQueryStrategyComparison/analysis.js";
|
|
3
|
+
import { RETRIEVAL_QUERY_STRATEGY_COMPARISON_SCOPE_IDS } from "../retrievalQueryStrategyComparison/analysisTypes.js";
|
|
4
|
+
import { CONTEXT_PACK_GENERATION_TREATMENT_IDS } from "./metadata.js";
|
|
5
|
+
const noMeasurementF1 = () => ({
|
|
6
|
+
availability: "unavailable",
|
|
7
|
+
numerator: null,
|
|
8
|
+
denominator: null,
|
|
9
|
+
value: null,
|
|
10
|
+
reason: "no-measurement"
|
|
11
|
+
});
|
|
12
|
+
/** Scores already-completed treatment evidence with the frozen retrieval-quality science. It never influences retrieval. */
|
|
13
|
+
export function analyzeContextPackGenerationTreatment(evaluationCase, treatment) {
|
|
14
|
+
if (treatment.identityEvidence === null || treatment.size === null) {
|
|
15
|
+
return {
|
|
16
|
+
treatmentId: treatment.treatmentId,
|
|
17
|
+
executionStatus: treatment.status,
|
|
18
|
+
availability: treatment.availability,
|
|
19
|
+
quality: null,
|
|
20
|
+
fileF1: noMeasurementF1(),
|
|
21
|
+
symbolF1: noMeasurementF1(),
|
|
22
|
+
estimatedTokens: null,
|
|
23
|
+
tokenCountMethod: null
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
const quality = calculateRetrievalQualityMetricsFromIdentityEvidence({
|
|
27
|
+
evaluationCase,
|
|
28
|
+
retrieval: {
|
|
29
|
+
identityEvidence: treatment.identityEvidence,
|
|
30
|
+
totalEstimatedTokens: treatment.size.totalEstimatedTokens,
|
|
31
|
+
tokenCountMethod: treatment.size.tokenCountMethod
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
return {
|
|
35
|
+
treatmentId: treatment.treatmentId,
|
|
36
|
+
executionStatus: treatment.status,
|
|
37
|
+
availability: treatment.availability,
|
|
38
|
+
quality,
|
|
39
|
+
fileF1: calculateBalancedIdentityF1(quality.file.relevantRetrievedFiles, quality.file.irrelevantRetrievedFiles, quality.file.missedFiles),
|
|
40
|
+
symbolF1: calculateBalancedIdentityF1(quality.symbol.relevantRetrievedSymbols, quality.symbol.irrelevantRetrievedSymbols, quality.symbol.missedSymbols),
|
|
41
|
+
estimatedTokens: treatment.size.totalEstimatedTokens,
|
|
42
|
+
tokenCountMethod: treatment.size.tokenCountMethod
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Same arithmetic as the existing repository token-savings comparison (tokensSaved = raw - other; percentSaved =
|
|
47
|
+
* tokensSaved / raw * 100, 0 when raw is 0). Negative savings are valid and never clamped.
|
|
48
|
+
*/
|
|
49
|
+
export function calculateContextPackTokenSavings(rawTokens, packTokens) {
|
|
50
|
+
const tokensSaved = rawTokens - packTokens;
|
|
51
|
+
return { tokensSaved, percentSaved: rawTokens === 0 ? 0 : (tokensSaved / rawTokens) * 100 };
|
|
52
|
+
}
|
|
53
|
+
const valueOf = (metric) => (metric && metric.availability === "available" ? metric.value : null);
|
|
54
|
+
function compareTreatments(raw, pack) {
|
|
55
|
+
const delta = (packValue, rawValue) => (packValue === null || rawValue === null ? null : packValue - rawValue);
|
|
56
|
+
const factPack = valueOf(pack.quality?.fact.coverage);
|
|
57
|
+
const factRaw = valueOf(raw.quality?.fact.coverage);
|
|
58
|
+
const sizes = raw.estimatedTokens !== null && pack.estimatedTokens !== null ? calculateContextPackTokenSavings(raw.estimatedTokens, pack.estimatedTokens) : null;
|
|
59
|
+
return {
|
|
60
|
+
fileF1Delta: delta(valueOf(pack.fileF1), valueOf(raw.fileF1)),
|
|
61
|
+
symbolF1Delta: delta(valueOf(pack.symbolF1), valueOf(raw.symbolF1)),
|
|
62
|
+
factCoverageDelta: delta(factPack, factRaw),
|
|
63
|
+
estimatedTokenDelta: delta(pack.estimatedTokens, raw.estimatedTokens),
|
|
64
|
+
tokensSaved: sizes?.tokensSaved ?? null,
|
|
65
|
+
percentSaved: sizes?.percentSaved ?? null
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
export function analyzeContextPackGenerationCase(evaluationCase, executionCase) {
|
|
69
|
+
if (evaluationCase.id !== executionCase.caseId)
|
|
70
|
+
throw new Error("Context pack generation analysis case identity mismatch.");
|
|
71
|
+
const order = executionCase.treatments.map((treatment) => treatment.treatmentId);
|
|
72
|
+
if (order.length !== CONTEXT_PACK_GENERATION_TREATMENT_IDS.length || order.some((id, index) => id !== CONTEXT_PACK_GENERATION_TREATMENT_IDS[index])) {
|
|
73
|
+
throw new Error("Context pack generation analysis requires the two canonical treatments in canonical order.");
|
|
74
|
+
}
|
|
75
|
+
const treatments = executionCase.treatments.map((treatment) => analyzeContextPackGenerationTreatment(evaluationCase, treatment));
|
|
76
|
+
return {
|
|
77
|
+
caseId: executionCase.caseId,
|
|
78
|
+
benchmarkProject: executionCase.benchmarkProject,
|
|
79
|
+
taskLocality: executionCase.taskLocality,
|
|
80
|
+
treatments,
|
|
81
|
+
comparison: compareTreatments(treatments[0], treatments[1])
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function isComparable(treatment) {
|
|
85
|
+
const quality = treatment.quality;
|
|
86
|
+
return (quality !== null &&
|
|
87
|
+
treatment.fileF1.availability === "available" &&
|
|
88
|
+
treatment.symbolF1.availability === "available" &&
|
|
89
|
+
quality.fact.coverage.availability === "available" &&
|
|
90
|
+
typeof quality.retrievedTokenCount === "number" &&
|
|
91
|
+
Number.isFinite(quality.retrievedTokenCount) &&
|
|
92
|
+
quality.retrievedTokenCount >= 0);
|
|
93
|
+
}
|
|
94
|
+
/** Matched design: a case counts only when BOTH treatments expose all four required measures. */
|
|
95
|
+
export function isContextPackCaseComparable(caseAnalysis) {
|
|
96
|
+
return caseAnalysis.treatments.length === CONTEXT_PACK_GENERATION_TREATMENT_IDS.length && caseAnalysis.treatments.every(isComparable);
|
|
97
|
+
}
|
|
98
|
+
const mean = (values) => values.reduce((sum, value) => sum + value, 0) / values.length;
|
|
99
|
+
function casesForScope(cases, scopeId) {
|
|
100
|
+
return scopeId === "overall" ? [...cases] : cases.filter((entry) => entry.taskLocality === scopeId);
|
|
101
|
+
}
|
|
102
|
+
export function aggregateContextPackGenerationScope(scopeId, cases) {
|
|
103
|
+
const scopeCases = casesForScope(cases, scopeId);
|
|
104
|
+
const included = scopeCases.filter(isContextPackCaseComparable);
|
|
105
|
+
const excluded = scopeCases.filter((entry) => !isContextPackCaseComparable(entry));
|
|
106
|
+
const treatmentSummaries = CONTEXT_PACK_GENERATION_TREATMENT_IDS.map((treatmentId, index) => {
|
|
107
|
+
const countStatus = (status) => scopeCases.filter((entry) => entry.treatments[index].executionStatus === status).length;
|
|
108
|
+
const rows = included.map((entry) => entry.treatments[index]);
|
|
109
|
+
return {
|
|
110
|
+
treatmentId,
|
|
111
|
+
completedCaseCount: countStatus("completed"),
|
|
112
|
+
partialCaseCount: countStatus("partial"),
|
|
113
|
+
failedCaseCount: countStatus("failed"),
|
|
114
|
+
objectives: rows.length === 0
|
|
115
|
+
? null
|
|
116
|
+
: {
|
|
117
|
+
meanFileF1: mean(rows.map((row) => row.fileF1.value)),
|
|
118
|
+
meanSymbolF1: mean(rows.map((row) => row.symbolF1.value)),
|
|
119
|
+
meanFactCoverage: mean(rows.map((row) => row.quality.fact.coverage.value)),
|
|
120
|
+
meanEstimatedTokenCount: mean(rows.map((row) => row.quality.retrievedTokenCount))
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
});
|
|
124
|
+
const [rawSummary, packSummary] = treatmentSummaries;
|
|
125
|
+
const rawObjectives = rawSummary.objectives;
|
|
126
|
+
const packObjectives = packSummary.objectives;
|
|
127
|
+
const paired = rawObjectives && packObjectives;
|
|
128
|
+
return {
|
|
129
|
+
scopeId,
|
|
130
|
+
caseCount: scopeCases.length,
|
|
131
|
+
includedCaseCount: included.length,
|
|
132
|
+
excludedCaseCount: excluded.length,
|
|
133
|
+
includedCaseIds: included.map((entry) => entry.caseId),
|
|
134
|
+
excludedCaseIds: excluded.map((entry) => entry.caseId),
|
|
135
|
+
treatmentSummaries,
|
|
136
|
+
pairedDeltas: paired
|
|
137
|
+
? {
|
|
138
|
+
meanFileF1Delta: mean(included.map((entry) => entry.comparison.fileF1Delta)),
|
|
139
|
+
meanSymbolF1Delta: mean(included.map((entry) => entry.comparison.symbolF1Delta)),
|
|
140
|
+
meanFactCoverageDelta: mean(included.map((entry) => entry.comparison.factCoverageDelta)),
|
|
141
|
+
meanEstimatedTokenDelta: mean(included.map((entry) => entry.comparison.estimatedTokenDelta))
|
|
142
|
+
}
|
|
143
|
+
: null,
|
|
144
|
+
tokenSavings: paired
|
|
145
|
+
? {
|
|
146
|
+
meanTokensSaved: mean(included.map((entry) => entry.comparison.tokensSaved)),
|
|
147
|
+
percentSavedOfMeans: calculateContextPackTokenSavings(rawObjectives.meanEstimatedTokenCount, packObjectives.meanEstimatedTokenCount).percentSaved
|
|
148
|
+
}
|
|
149
|
+
: null
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
export function analyzeContextPackGeneration(evaluationCases, executionCases) {
|
|
153
|
+
if (evaluationCases.length !== executionCases.length)
|
|
154
|
+
throw new Error("Context pack generation analysis requires one execution case per evaluation case.");
|
|
155
|
+
const cases = evaluationCases.map((evaluationCase, index) => analyzeContextPackGenerationCase(evaluationCase, executionCases[index]));
|
|
156
|
+
return { cases, scopes: RETRIEVAL_QUERY_STRATEGY_COMPARISON_SCOPE_IDS.map((scopeId) => aggregateContextPackGenerationScope(scopeId, cases)) };
|
|
157
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export const CONTEXT_PACK_GENERATION_ANALYSIS_ARTIFACT_FILE = "context-pack-generation-analysis.json";
|
|
2
|
+
export const CONTEXT_PACK_GENERATION_ANALYSIS_SCHEMA_VERSION = "my-dev-kit-lab-context-pack-generation-analysis-v1";
|
|
3
|
+
/** Single owner of the frozen methodology. No ranking, winner, composite score or Pareto front exists for this experiment. */
|
|
4
|
+
export const CONTEXT_PACK_GENERATION_METHODOLOGY = {
|
|
5
|
+
fileF1: "balanced-f1",
|
|
6
|
+
symbolF1: "balanced-f1",
|
|
7
|
+
aggregation: "matched-complete-case-macro-mean",
|
|
8
|
+
treatmentComparison: "paired-descriptive-delta",
|
|
9
|
+
sizeMeasure: "estimated-tokens-of-rendered-text"
|
|
10
|
+
};
|
|
11
|
+
export function buildContextPackGenerationAnalysisArtifact(args) {
|
|
12
|
+
return {
|
|
13
|
+
schemaVersion: CONTEXT_PACK_GENERATION_ANALYSIS_SCHEMA_VERSION,
|
|
14
|
+
runId: args.runId,
|
|
15
|
+
pluginId: args.pluginId,
|
|
16
|
+
pluginSchemaVersion: args.pluginSchemaVersion,
|
|
17
|
+
startedAt: args.startedAt,
|
|
18
|
+
completedAt: args.completedAt,
|
|
19
|
+
methodology: { ...CONTEXT_PACK_GENERATION_METHODOLOGY },
|
|
20
|
+
analysis: structuredClone(args.analysis)
|
|
21
|
+
};
|
|
22
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { countEstimatedTokens, countTextChars, tokenCountMethod } from "../../../core/countTokens.js";
|
|
2
|
+
import { compareCodeUnits } from "../../../evaluation/indexSnapshot.js";
|
|
3
|
+
import { buildContextPackSelectionPolicy, selectCallRelationships, selectRelevantFiles, selectRelevantSymbols, selectSourceSlices, selectTestFiles } from "./packSelectionPolicy.js";
|
|
4
|
+
import { renderContextPack } from "./renderContextPack.js";
|
|
5
|
+
import { CONTEXT_PACK_SCHEMA_VERSION, CONTEXT_PACK_SECTION_IDS } from "./types.js";
|
|
6
|
+
/**
|
|
7
|
+
* Pure context-pack composition. No filesystem, process, environment, network, clock or randomness: identical input
|
|
8
|
+
* yields structurally identical output. It selects within the frozen caps, renders deterministically, and measures size
|
|
9
|
+
* from the exact rendered text.
|
|
10
|
+
*/
|
|
11
|
+
export function buildContextPack(input) {
|
|
12
|
+
const files = selectRelevantFiles(input.fileCandidates);
|
|
13
|
+
const symbols = selectRelevantSymbols(input.symbolCandidates);
|
|
14
|
+
const sourceSlices = selectSourceSlices(input.sourceSliceCandidates);
|
|
15
|
+
const calls = selectCallRelationships(input.callCandidates);
|
|
16
|
+
const tests = selectTestFiles(input.testCandidates);
|
|
17
|
+
const evidenceNotes = [
|
|
18
|
+
...new Set([
|
|
19
|
+
...(input.evidenceNotes ?? []),
|
|
20
|
+
...files.notes,
|
|
21
|
+
...symbols.notes,
|
|
22
|
+
...sourceSlices.notes,
|
|
23
|
+
...calls.notes,
|
|
24
|
+
...tests.notes
|
|
25
|
+
])
|
|
26
|
+
].sort(compareCodeUnits);
|
|
27
|
+
const sectionAvailability = {
|
|
28
|
+
task: { availability: "available", reason: null },
|
|
29
|
+
files: { ...input.sectionAvailability.files },
|
|
30
|
+
symbols: { ...input.sectionAvailability.symbols },
|
|
31
|
+
sourceSlices: { ...input.sectionAvailability.sourceSlices },
|
|
32
|
+
callRelationships: { ...input.sectionAvailability.callRelationships },
|
|
33
|
+
tests: { ...input.sectionAvailability.tests },
|
|
34
|
+
evidenceNotes: { ...input.sectionAvailability.evidenceNotes }
|
|
35
|
+
};
|
|
36
|
+
const itemCounts = {
|
|
37
|
+
task: 1,
|
|
38
|
+
files: files.items.length,
|
|
39
|
+
symbols: symbols.items.length,
|
|
40
|
+
sourceSlices: sourceSlices.items.length,
|
|
41
|
+
callRelationships: calls.items.length,
|
|
42
|
+
tests: tests.items.length,
|
|
43
|
+
evidenceNotes: evidenceNotes.length
|
|
44
|
+
};
|
|
45
|
+
const rendered = renderContextPack({
|
|
46
|
+
task: input.task,
|
|
47
|
+
files: files.items,
|
|
48
|
+
symbols: symbols.items,
|
|
49
|
+
sourceSlices: sourceSlices.items,
|
|
50
|
+
callRelationships: calls.items,
|
|
51
|
+
tests: tests.items,
|
|
52
|
+
evidenceNotes,
|
|
53
|
+
sectionAvailability
|
|
54
|
+
});
|
|
55
|
+
return {
|
|
56
|
+
schemaVersion: CONTEXT_PACK_SCHEMA_VERSION,
|
|
57
|
+
caseId: input.caseId,
|
|
58
|
+
benchmarkProject: input.benchmarkProject,
|
|
59
|
+
taskLocality: input.taskLocality,
|
|
60
|
+
task: { title: input.task.title, summary: input.task.summary },
|
|
61
|
+
selectionPolicy: buildContextPackSelectionPolicy(input.myDevKitVersion),
|
|
62
|
+
files: files.items,
|
|
63
|
+
symbols: symbols.items,
|
|
64
|
+
sourceSlices: sourceSlices.items,
|
|
65
|
+
callRelationships: calls.items,
|
|
66
|
+
tests: tests.items,
|
|
67
|
+
evidenceNotes,
|
|
68
|
+
sections: CONTEXT_PACK_SECTION_IDS.map((id) => ({
|
|
69
|
+
id,
|
|
70
|
+
availability: sectionAvailability[id].availability,
|
|
71
|
+
reason: sectionAvailability[id].reason,
|
|
72
|
+
itemCount: itemCounts[id],
|
|
73
|
+
estimatedTokens: countEstimatedTokens(rendered.sections[id])
|
|
74
|
+
})),
|
|
75
|
+
size: {
|
|
76
|
+
totalChars: countTextChars(rendered.renderedText),
|
|
77
|
+
totalEstimatedTokens: countEstimatedTokens(rendered.renderedText),
|
|
78
|
+
tokenCountMethod
|
|
79
|
+
},
|
|
80
|
+
availability: input.availability,
|
|
81
|
+
reason: input.reason,
|
|
82
|
+
renderedText: rendered.renderedText
|
|
83
|
+
};
|
|
84
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { invalidExperimentConfig, isPlainObject, mergeConfig, validExperimentConfig } from "../../config.js";
|
|
2
|
+
export const defaultContextPackGenerationConfig = {
|
|
3
|
+
outDir: "lab-output/context-pack-generation",
|
|
4
|
+
kitCommand: "npx @dailephd/my-dev-kit@latest"
|
|
5
|
+
};
|
|
6
|
+
const SUPPORTED_FIELDS = ["outDir", "kitCommand", "caseIds", "benchmarkProjects"];
|
|
7
|
+
export const contextPackGenerationConfigDefinition = {
|
|
8
|
+
fields: [
|
|
9
|
+
{ name: "outDir", type: "string", required: true, description: "Experiment output root." },
|
|
10
|
+
{ name: "kitCommand", type: "string", description: "my-dev-kit command used for index and retrieval." },
|
|
11
|
+
{ name: "caseIds", type: "array", description: "Bundled evaluation case IDs to include; corpus order is preserved." },
|
|
12
|
+
{ name: "benchmarkProjects", type: "array", description: "Bundled benchmark project IDs to include; corpus order is preserved." }
|
|
13
|
+
]
|
|
14
|
+
};
|
|
15
|
+
export function validateContextPackGenerationConfig(config) {
|
|
16
|
+
if (config !== undefined && !isPlainObject(config)) {
|
|
17
|
+
return invalidExperimentConfig(["context pack generation config must be an object."]);
|
|
18
|
+
}
|
|
19
|
+
const errors = [];
|
|
20
|
+
const unsupported = Object.keys(config ?? {}).filter((key) => !SUPPORTED_FIELDS.includes(key));
|
|
21
|
+
if (unsupported.length > 0) {
|
|
22
|
+
errors.push(`Unsupported context-pack-generation config field(s): ${unsupported.sort().join(", ")}.`);
|
|
23
|
+
}
|
|
24
|
+
const normalized = mergeConfig(defaultContextPackGenerationConfig, config);
|
|
25
|
+
for (const field of ["outDir", "kitCommand"]) {
|
|
26
|
+
const value = normalized[field];
|
|
27
|
+
if (typeof value !== "string" || !value.trim())
|
|
28
|
+
errors.push(`${field} must be a non-empty string.`);
|
|
29
|
+
}
|
|
30
|
+
for (const field of ["caseIds", "benchmarkProjects"]) {
|
|
31
|
+
const value = normalized[field];
|
|
32
|
+
if (value === undefined)
|
|
33
|
+
continue;
|
|
34
|
+
if (!Array.isArray(value) || value.length === 0 || !value.every((item) => typeof item === "string" && item.trim())) {
|
|
35
|
+
errors.push(`${field} must be a non-empty array of non-empty strings when provided.`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return errors.length > 0 ? invalidExperimentConfig(errors) : validExperimentConfig(normalized);
|
|
39
|
+
}
|