@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.
Files changed (40) hide show
  1. package/CHANGELOG.md +10 -1
  2. package/README.md +5 -4
  3. package/dist/src/cli/help.js +16 -1
  4. package/dist/src/commands/runExperimentDescribeCommand.js +8 -1
  5. package/dist/src/commands/runExperimentRunCommand.js +90 -3
  6. package/dist/src/evaluation/runMyDevKitRetrieval.js +1 -0
  7. package/dist/src/experiments/defaultRegistry.js +2 -0
  8. package/dist/src/experiments/plugins/contextPackGeneration/analysis.js +157 -0
  9. package/dist/src/experiments/plugins/contextPackGeneration/analysisArtifact.js +22 -0
  10. package/dist/src/experiments/plugins/contextPackGeneration/analysisTypes.js +1 -0
  11. package/dist/src/experiments/plugins/contextPackGeneration/buildContextPack.js +84 -0
  12. package/dist/src/experiments/plugins/contextPackGeneration/config.js +39 -0
  13. package/dist/src/experiments/plugins/contextPackGeneration/execution.js +361 -0
  14. package/dist/src/experiments/plugins/contextPackGeneration/executionArtifact.js +17 -0
  15. package/dist/src/experiments/plugins/contextPackGeneration/executionTypes.js +11 -0
  16. package/dist/src/experiments/plugins/contextPackGeneration/identityEvidence.js +71 -0
  17. package/dist/src/experiments/plugins/contextPackGeneration/index.js +19 -0
  18. package/dist/src/experiments/plugins/contextPackGeneration/localSubjectExecution.js +146 -0
  19. package/dist/src/experiments/plugins/contextPackGeneration/localSubjectPrivacy.js +276 -0
  20. package/dist/src/experiments/plugins/contextPackGeneration/metadata.js +24 -0
  21. package/dist/src/experiments/plugins/contextPackGeneration/metrics.js +46 -0
  22. package/dist/src/experiments/plugins/contextPackGeneration/packArtifact.js +17 -0
  23. package/dist/src/experiments/plugins/contextPackGeneration/packEvidence.js +152 -0
  24. package/dist/src/experiments/plugins/contextPackGeneration/packSelectionPolicy.js +305 -0
  25. package/dist/src/experiments/plugins/contextPackGeneration/plugin.js +355 -0
  26. package/dist/src/experiments/plugins/contextPackGeneration/renderContextPack.js +61 -0
  27. package/dist/src/experiments/plugins/contextPackGeneration/types.js +29 -0
  28. package/dist/src/report/experiments/buildContextPackGenerationReport.js +171 -0
  29. package/dist/src/report/experiments/buildPluginExperimentReport.js +24 -2
  30. package/dist/src/report/experiments/contextPackGenerationReportModel.js +9 -0
  31. package/dist/src/report/experiments/index.js +5 -0
  32. package/dist/src/report/experiments/loadContextPackArtifacts.js +43 -0
  33. package/dist/src/report/experiments/renderContextPackGenerationHtml.js +122 -0
  34. package/dist/src/report/experiments/renderContextPackGenerationText.js +101 -0
  35. package/dist/src/report/experiments/renderPluginExperimentReportHtml.js +3 -0
  36. package/dist/src/report/experiments/renderPluginExperimentReportText.js +7 -0
  37. package/dist/src/report/experiments/renderRetrievalQueryStrategyComparisonHtml.js +15 -15
  38. package/dist/src/report/experiments/writePluginExperimentReports.js +2 -0
  39. package/docs/METRICS.md +28 -1
  40. 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.1] - 2026-10-05
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.1 (retrieval query strategy comparison), and the published package and installed CLI are version 0.8.1. The previous release is v0.8.0 (retrieval precision/recall). 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.
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 (context-pack generation experiments) is the next planned milestone.
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 six registered plugins: `context-strategy-comparison`, `warm-index-reuse`, `incremental-change-staleness`, `context-window-scaling`, `retrieval-precision-recall`, and `retrieval-query-strategy-comparison`. The v0.7.0 context-window-scaling plugin is released; v0.7.1 lets it also run over caller-supplied synthetic repositories; v0.7.2 lets it run over an explicitly selected local Git repository. The fifth, `retrieval-precision-recall`, was added in v0.8.0. The sixth, `retrieval-query-strategy-comparison`, was added in v0.8.1; agent-success plugins remain planned.
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, selects no strategy automatically, and generates no context pack. 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
+ - `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.
@@ -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, and retrieval-query-strategy-comparison):",
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
- throw new Error(`--local-subject-config is only supported for --experiment ${contextWindowScalingPlugin.metadata.id}, ${retrievalPrecisionRecallPlugin.metadata.id}, or ${retrievalQueryStrategyComparisonPlugin.metadata.id}.`);
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,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
+ }