@dailephd/my-dev-kit-lab 0.6.1 → 0.6.3

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 (61) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +9 -7
  3. package/benchmarks/contracts/incremental-change-staleness-scenarios.json +360 -0
  4. package/dist/src/cli/help.js +16 -9
  5. package/dist/src/commands/runExperimentDescribeCommand.js +5 -2
  6. package/dist/src/commands/runExperimentRunCommand.js +4 -7
  7. package/dist/src/evaluation/affectedNeighborhoodMetrics.js +51 -0
  8. package/dist/src/evaluation/benchmarkMetadata.js +5 -1
  9. package/dist/src/evaluation/parseAgentAnswer.js +172 -24
  10. package/dist/src/evaluation/runMyDevKitRetrieval.js +120 -7
  11. package/dist/src/experiments/defaultRegistry.js +2 -0
  12. package/dist/src/experiments/plugins/incrementalChangeStaleness/comparison.js +101 -0
  13. package/dist/src/experiments/plugins/incrementalChangeStaleness/comparisonV2.js +49 -0
  14. package/dist/src/experiments/plugins/incrementalChangeStaleness/config.js +67 -0
  15. package/dist/src/experiments/plugins/incrementalChangeStaleness/disposableTarget.js +125 -0
  16. package/dist/src/experiments/plugins/incrementalChangeStaleness/execution.js +430 -0
  17. package/dist/src/experiments/plugins/incrementalChangeStaleness/executionArtifact.js +160 -0
  18. package/dist/src/experiments/plugins/incrementalChangeStaleness/executionArtifactV2.js +289 -0
  19. package/dist/src/experiments/plugins/incrementalChangeStaleness/executionV2.js +195 -0
  20. package/dist/src/experiments/plugins/incrementalChangeStaleness/index.js +13 -0
  21. package/dist/src/experiments/plugins/incrementalChangeStaleness/indexClone.js +76 -0
  22. package/dist/src/experiments/plugins/incrementalChangeStaleness/lifecycle.js +308 -0
  23. package/dist/src/experiments/plugins/incrementalChangeStaleness/lifecyclePolicy.js +154 -0
  24. package/dist/src/experiments/plugins/incrementalChangeStaleness/lifecyclePolicyV2.js +78 -0
  25. package/dist/src/experiments/plugins/incrementalChangeStaleness/lifecycleV2.js +298 -0
  26. package/dist/src/experiments/plugins/incrementalChangeStaleness/mutationExecution.js +144 -0
  27. package/dist/src/experiments/plugins/incrementalChangeStaleness/plugin.js +475 -0
  28. package/dist/src/experiments/plugins/incrementalChangeStaleness/scenarioCatalog.js +42 -0
  29. package/dist/src/experiments/plugins/incrementalChangeStaleness/scenarioMutation.js +116 -0
  30. package/dist/src/experiments/plugins/incrementalChangeStaleness/scenarioSelection.js +79 -0
  31. package/dist/src/experiments/plugins/incrementalChangeStaleness/scenarioTypes.js +37 -0
  32. package/dist/src/experiments/plugins/incrementalChangeStaleness/scenarioValidation.js +168 -0
  33. package/dist/src/experiments/plugins/incrementalChangeStaleness/sourceState.js +145 -0
  34. package/dist/src/experiments/plugins/incrementalChangeStaleness/treatmentSession.js +6 -0
  35. package/dist/src/experiments/plugins/incrementalChangeStaleness/treatmentSessionV2.js +5 -0
  36. package/dist/src/experiments/plugins/index.js +1 -0
  37. package/dist/src/experiments/plugins/warmIndexReuse/metrics.js +17 -20
  38. package/dist/src/experiments/plugins/warmIndexReuse/warmIndexSession.js +3 -1
  39. package/dist/src/mobile/android/advancedSecurity/candidateEvidence.js +9 -2
  40. package/dist/src/mobile/android/advancedSecurity/redaction.js +19 -10
  41. package/dist/src/mobile/android/advancedSecurity/secretCandidates/analyzeSecretCandidates.js +12 -2
  42. package/dist/src/mobile/android/advancedSecurity/signingConfiguration/analyzeSigningConfiguration.js +1 -1
  43. package/dist/src/mobile/android/advancedSecurity/signingConfiguration/extractSigningConfigurations.js +7 -7
  44. package/dist/src/report/experiments/buildIncrementalChangeStalenessPluginReport.js +33 -0
  45. package/dist/src/report/experiments/buildIncrementalChangeStalenessReport.js +112 -0
  46. package/dist/src/report/experiments/buildIncrementalChangeStalenessReportV2.js +80 -0
  47. package/dist/src/report/experiments/buildPluginExperimentReport.js +3 -0
  48. package/dist/src/report/experiments/incrementalChangeStalenessReportModel.js +4 -0
  49. package/dist/src/report/experiments/incrementalChangeStalenessReportModelV2.js +3 -0
  50. package/dist/src/report/experiments/incrementalChangeStalenessReportRowsV2.js +146 -0
  51. package/dist/src/report/experiments/index.js +6 -0
  52. package/dist/src/report/experiments/renderIncrementalChangeStalenessHtml.js +243 -0
  53. package/dist/src/report/experiments/renderIncrementalChangeStalenessHtmlV2.js +111 -0
  54. package/dist/src/report/experiments/renderIncrementalChangeStalenessTextV2.js +96 -0
  55. package/dist/src/report/experiments/renderPluginExperimentReportHtml.js +3 -0
  56. package/dist/src/report/experiments/renderPluginExperimentReportText.js +144 -0
  57. package/dist/src/securityValidation/staticScans/codeql.js +8 -8
  58. package/docs/METRICS.md +67 -1
  59. package/examples/tutorial-browser/prepare.mjs +13 -13
  60. package/examples/tutorial-browser/server.mjs +13 -13
  61. package/package.json +1 -1
@@ -44,17 +44,17 @@ function extractStoreFileValue(content) {
44
44
  return { state: "literal", literalValue: fileWrapped[1] };
45
45
  return extractPathValue(content, ["storeFile"]);
46
46
  }
47
- function extractCredentialValue(content, keys) {
47
+ function extractCredentialValue(content, keys, context) {
48
48
  const result = extractLiteral(content, keys);
49
49
  const state = classifyExpressionState(result);
50
50
  if (state === "literal" && result.value !== undefined) {
51
- return { state, redactedPreview: redactedPreviewForCandidate(result.value), fingerprint: fingerprintCandidateValue(result.value) };
51
+ return { state, redactedPreview: redactedPreviewForCandidate(result.value), fingerprint: fingerprintCandidateValue(result.value, context) };
52
52
  }
53
53
  if (result.raw !== undefined)
54
54
  return { state, rawExpression: boundedRawExpression(result.raw) };
55
55
  return { state: "missing" };
56
56
  }
57
- function extractSigningConfigEntry(name, content) {
57
+ function extractSigningConfigEntry(name, content, sourcePath) {
58
58
  const v1 = extractBooleanLiteral(content, ["enableV1Signing", "v1SigningEnabled"]);
59
59
  const v2 = extractBooleanLiteral(content, ["enableV2Signing", "v2SigningEnabled"]);
60
60
  const v3 = extractBooleanLiteral(content, ["enableV3Signing", "v3SigningEnabled"]);
@@ -62,9 +62,9 @@ function extractSigningConfigEntry(name, content) {
62
62
  return {
63
63
  name,
64
64
  storeFile: extractStoreFileValue(content),
65
- storePassword: extractCredentialValue(content, ["storePassword"]),
65
+ storePassword: extractCredentialValue(content, ["storePassword"], { ruleId: "android-signing-password-literal", sourcePath, purpose: `signing-credential:${name}:storePassword` }),
66
66
  keyAlias: extractPathValue(content, ["keyAlias"]),
67
- keyPassword: extractCredentialValue(content, ["keyPassword"]),
67
+ keyPassword: extractCredentialValue(content, ["keyPassword"], { ruleId: "android-signing-password-literal", sourcePath, purpose: `signing-credential:${name}:keyPassword` }),
68
68
  enableV1Signing: v1.value,
69
69
  enableV2Signing: v2.value,
70
70
  enableV3Signing: v3.value,
@@ -73,9 +73,9 @@ function extractSigningConfigEntry(name, content) {
73
73
  }
74
74
  // Extracts every named entry inside a module build file's `signingConfigs { }`
75
75
  // block. Returns an empty array (never throws) when no such block exists.
76
- export function extractSigningConfigurations(rawBuildFileText) {
76
+ export function extractSigningConfigurations(rawBuildFileText, context) {
77
77
  const text = stripComments(rawBuildFileText);
78
78
  return extractNamedSubBlocks(text, "signingConfigs")
79
- .map(({ name, content }) => extractSigningConfigEntry(name, content))
79
+ .map(({ name, content }) => extractSigningConfigEntry(name, content, context.sourcePath))
80
80
  .sort((a, b) => a.name.localeCompare(b.name));
81
81
  }
@@ -0,0 +1,33 @@
1
+ // Dispatches the incremental-change-staleness plugin report by the persisted execution artifact's
2
+ // schemaVersion: V1 artifacts keep the released V1 report semantics untouched (the V1 builder is called
3
+ // as-is), V2 artifacts select the four-treatment V2 model. Historical V1 evidence is never converted.
4
+ import { readFileSync } from "node:fs";
5
+ import { INCREMENTAL_CHANGE_STALENESS_EXECUTION_SCHEMA_VERSION } from "../../experiments/plugins/incrementalChangeStaleness/executionArtifact.js";
6
+ import { INCREMENTAL_CHANGE_STALENESS_EXECUTION_SCHEMA_VERSION_V2 } from "../../experiments/plugins/incrementalChangeStaleness/executionArtifactV2.js";
7
+ import { INCREMENTAL_CHANGE_STALENESS_PLUGIN_ID } from "../../experiments/plugins/incrementalChangeStaleness/plugin.js";
8
+ import { buildIncrementalChangeStalenessReport, readArtifactPath } from "./buildIncrementalChangeStalenessReport.js";
9
+ import { buildIncrementalChangeStalenessReportV2FromArtifact } from "./buildIncrementalChangeStalenessReportV2.js";
10
+ export function buildIncrementalChangeStalenessPluginReport(run) {
11
+ if (run.pluginId !== INCREMENTAL_CHANGE_STALENESS_PLUGIN_ID)
12
+ return null;
13
+ const artifactPath = readArtifactPath(run);
14
+ if (!artifactPath)
15
+ return buildIncrementalChangeStalenessReport(run); // throws the established missing-path error
16
+ let schemaVersion;
17
+ let parsed;
18
+ try {
19
+ parsed = JSON.parse(readFileSync(artifactPath, "utf8"));
20
+ schemaVersion = parsed?.schemaVersion;
21
+ }
22
+ catch {
23
+ // Unreadable or malformed: the V1 builder owns the established, explicit error messages.
24
+ return buildIncrementalChangeStalenessReport(run);
25
+ }
26
+ if (schemaVersion === INCREMENTAL_CHANGE_STALENESS_EXECUTION_SCHEMA_VERSION_V2) {
27
+ return buildIncrementalChangeStalenessReportV2FromArtifact(parsed);
28
+ }
29
+ if (schemaVersion === INCREMENTAL_CHANGE_STALENESS_EXECUTION_SCHEMA_VERSION) {
30
+ return buildIncrementalChangeStalenessReport(run);
31
+ }
32
+ throw new Error(`Invalid incremental-change-staleness report source: unsupported execution artifact schema version ${String(schemaVersion)}.`);
33
+ }
@@ -0,0 +1,112 @@
1
+ // v0.6.2 Batch 5 -- projects the persisted Batch 4 incremental-change-staleness-execution.json
2
+ // artifact into the plugin report model. This is a deterministic projection only: no retrieval,
3
+ // no fake-agent evaluation, no correctness scoring, no affected-neighborhood assessment, no
4
+ // freshness assessment, no stale-risk classification, and no source/index/benchmark reads happen
5
+ // here. The persisted artifact is the only plugin-specific evidence source (frozen batch plan
6
+ // Section 4); this file's only I/O is reading that one JSON file.
7
+ import { readFileSync } from "node:fs";
8
+ import { INCREMENTAL_CHANGE_STALENESS_EXECUTION_SCHEMA_VERSION } from "../../experiments/plugins/incrementalChangeStaleness/executionArtifact.js";
9
+ import { INCREMENTAL_CHANGE_STALENESS_PLUGIN_ID } from "../../experiments/plugins/incrementalChangeStaleness/plugin.js";
10
+ import { INCREMENTAL_CHANGE_STALENESS_REPORT_SCHEMA_VERSION } from "./incrementalChangeStalenessReportModel.js";
11
+ // Frozen wording (Section 25). Every point must appear in report.txt and report.html.
12
+ const LIMITATIONS = [
13
+ "Results are scoped to the executed benchmark scenarios, deterministic controlled changes, configured my-dev-kit version, and observed retrieval/evaluation evidence.",
14
+ "\"no-observed-stale-regression\" means only that the frozen comparison did not observe a stale-specific regression in that matched run. It does not prove stale indexes are generally safe.",
15
+ "\"reindexRecommendation\" is separate affected-neighborhood observational evidence; it did not select or trigger either experiment treatment.",
16
+ "Required-file evidence is a bounded answer-key file-presence check, not retrieval precision/recall.",
17
+ "Correctness is deterministic fake-agent/harness evidence unless a future version explicitly adds real-agent evidence.",
18
+ "v0.6.2 compares stale full-index reuse with complete full refresh only.",
19
+ "Partial refresh is not evaluated in v0.6.2.",
20
+ "The experiment does not use graph-diff.",
21
+ "\"changedSymbolCount\" does not mean source-level symbol-diff proof; it retains the released v0.6.1 definition."
22
+ ];
23
+ /** Fixed neutral wording per stale-risk classification category (Section 21). Presentation only. */
24
+ export const STALE_RISK_CLASSIFICATION_EXPLANATIONS = {
25
+ "observed-stale-regression": "The persisted matched comparison observed at least one stale-specific regression under the frozen v0.6.2 evidence dimensions: correctness and/or required-file presence.",
26
+ "no-observed-stale-regression": "This matched scenario/run did not observe a stale-specific regression under the frozen correctness and required-file evidence dimensions. This does not establish general stale-index safety and does not imply that reindexing should be skipped.",
27
+ inconclusive: "Available persisted evidence was insufficient to determine whether a stale-specific regression occurred under the frozen comparison rules. Inconclusive is not the same as no regression."
28
+ };
29
+ /**
30
+ * Builds the plugin report section for an incremental-change-staleness run, or `null` for any
31
+ * other plugin. Every field is a deterministic projection of the persisted Batch 4 execution
32
+ * artifact; this function performs no scientific recomputation of its own.
33
+ */
34
+ export function buildIncrementalChangeStalenessReport(run) {
35
+ if (run.pluginId !== INCREMENTAL_CHANGE_STALENESS_PLUGIN_ID)
36
+ return null;
37
+ const artifact = loadExecutionArtifact(run);
38
+ const scenarios = artifact.scenarios.map(toReportScenario);
39
+ return {
40
+ schemaVersion: INCREMENTAL_CHANGE_STALENESS_REPORT_SCHEMA_VERSION,
41
+ pluginId: artifact.pluginId,
42
+ scenarioCount: artifact.summary.scenarioCount,
43
+ readyScenarioCount: artifact.summary.readyScenarioCount,
44
+ failedScenarioCount: artifact.summary.failedScenarioCount,
45
+ observedStaleRegressionCount: artifact.summary.observedStaleRegressionCount,
46
+ noObservedStaleRegressionCount: artifact.summary.noObservedStaleRegressionCount,
47
+ inconclusiveCount: artifact.summary.inconclusiveCount,
48
+ scenarios,
49
+ limitations: [...LIMITATIONS]
50
+ };
51
+ }
52
+ function toReportScenario(record) {
53
+ return {
54
+ scenarioId: record.scenarioId,
55
+ category: record.category,
56
+ benchmarkProjectId: record.benchmarkProjectId,
57
+ baseCaseId: record.baseCaseId,
58
+ status: record.status,
59
+ failureReason: record.failureReason,
60
+ query: record.query,
61
+ answerPolicy: record.answerPolicy,
62
+ expectedFiles: [...record.expectedFiles],
63
+ expectedSymbols: [...record.expectedSymbols],
64
+ lifecycle: record.lifecycle
65
+ ? {
66
+ ...record.lifecycle,
67
+ controlledFilePaths: [...record.lifecycle.controlledFilePaths],
68
+ sourceRoots: [...record.lifecycle.sourceRoots]
69
+ }
70
+ : null,
71
+ // Section 12/34: always stale-index then full-refresh, never combined, never resorted.
72
+ staleTreatment: record.stale ? structuredClone(record.stale) : null,
73
+ fullRefreshTreatment: record.fullRefresh ? structuredClone(record.fullRefresh) : null,
74
+ comparison: { ...record.comparison, reasonCodes: [...record.comparison.reasonCodes] }
75
+ };
76
+ }
77
+ export function readArtifactPath(run) {
78
+ const fromMetadata = run.metadata?.executionArtifactPath;
79
+ if (typeof fromMetadata === "string" && fromMetadata)
80
+ return fromMetadata;
81
+ return run.artifacts.find((artifact) => artifact.id === "incremental-change-staleness-execution")?.path ?? undefined;
82
+ }
83
+ /**
84
+ * The persisted execution artifact is the sole plugin-specific evidence source for this report
85
+ * (Section 4/46). A missing, unreadable, malformed, or schema-mismatched artifact is a report
86
+ * construction error, never silently reconstructed from source/index/retrieval state.
87
+ */
88
+ function loadExecutionArtifact(run) {
89
+ const artifactPath = readArtifactPath(run);
90
+ if (!artifactPath) {
91
+ throw new Error("Invalid incremental-change-staleness report source: no execution artifact path was found on the run.");
92
+ }
93
+ let raw;
94
+ try {
95
+ raw = readFileSync(artifactPath, "utf8");
96
+ }
97
+ catch (error) {
98
+ throw new Error(`Invalid incremental-change-staleness report source: execution artifact could not be read at ${artifactPath}: ${error instanceof Error ? error.message : String(error)}`);
99
+ }
100
+ let parsed;
101
+ try {
102
+ parsed = JSON.parse(raw);
103
+ }
104
+ catch {
105
+ throw new Error(`Invalid incremental-change-staleness report source: execution artifact at ${artifactPath} is not valid JSON.`);
106
+ }
107
+ const candidate = parsed;
108
+ if (candidate.schemaVersion !== INCREMENTAL_CHANGE_STALENESS_EXECUTION_SCHEMA_VERSION) {
109
+ throw new Error(`Invalid incremental-change-staleness report source: unsupported execution artifact schema version ${String(candidate.schemaVersion)}.`);
110
+ }
111
+ return candidate;
112
+ }
@@ -0,0 +1,80 @@
1
+ // v0.6.3 -- projects the persisted V2 incremental-change-staleness execution artifact into the V2 report
2
+ // model. A deterministic projection only: no retrieval, fake-agent evaluation, correctness scoring,
3
+ // affected-neighborhood assessment, freshness assessment, refresh-realization or classification
4
+ // recomputation, and no source/index/benchmark reads. The only integrity check is the artifact's own
5
+ // contradiction validator, so a hand-edited or corrupted artifact is rejected rather than presented.
6
+ import { validateIncrementalChangeStalenessScenarioRecordV2 } from "../../experiments/plugins/incrementalChangeStaleness/executionArtifactV2.js";
7
+ import { INCREMENTAL_CHANGE_STALENESS_V2_TREATMENT_IDS } from "../../experiments/plugins/incrementalChangeStaleness/disposableTarget.js";
8
+ import { INCREMENTAL_CHANGE_STALENESS_REPORT_SCHEMA_VERSION_V2 } from "./incrementalChangeStalenessReportModelV2.js";
9
+ // Frozen V2 wording. Every point must appear in report.txt and report.html.
10
+ export const LIMITATIONS_V2 = [
11
+ "Results are scoped to the executed deterministic benchmark scenarios and the configured upstream my-dev-kit tool.",
12
+ "Full refresh is a comparison reference, not an asserted optimal strategy.",
13
+ "A partial treatment that falls back to a full rebuild is not evidence about the requested partial-refresh behavior.",
14
+ "\"no-observed-regression-relative-to-full\" means only that the executed scenario did not show a regression under the frozen correctness and required-file evidence dimensions. It does not prove general equivalence or safety, and does not show that full refresh is unnecessary.",
15
+ "Required-file evidence is a bounded answer-key file-presence check, not retrieval precision/recall.",
16
+ "Correctness remains deterministic fake-agent/harness evidence in v0.6.3.",
17
+ "Lab affected-neighborhood evidence (changed baseline neighborhood relative to the task) is distinct from upstream refresh execution evidence (what my-dev-kit actually refreshed).",
18
+ "\"reindexRecommendation\" remains observational evidence; it did not select or trigger any treatment.",
19
+ "\"changedSymbolCount\" does not mean source-level symbol-diff proof; it retains the released v0.6.1 baseline-symbol-identity definition.",
20
+ "The experiment does not use graph-diff."
21
+ ];
22
+ /** Fixed neutral wording per partial-refresh reference classification. Presentation only. */
23
+ export const PARTIAL_REFERENCE_CLASSIFICATION_EXPLANATIONS = {
24
+ "observed-regression-relative-to-full": "The persisted comparison observed at least one regression relative to the full-refresh reference under the frozen evidence dimensions: correctness and/or required-file presence.",
25
+ "no-observed-regression-relative-to-full": "This executed scenario did not show a regression relative to the full-refresh reference under the frozen correctness and required-file evidence dimensions. This does not establish general equivalence or safety, and does not imply that full refresh is unnecessary.",
26
+ inconclusive: "Available persisted evidence was insufficient to determine whether a regression relative to the full-refresh reference occurred. Inconclusive is not the same as no regression.",
27
+ "not-comparable-as-partial-refresh": "The requested partial-refresh treatment fell back to a full rebuild, so this run does not provide evidence about the requested partial-refresh behavior."
28
+ };
29
+ /** Presentation order and labels; identity is always resolved by treatment id, never by array position. */
30
+ export const TREATMENT_PRESENTATION = [
31
+ { id: "stale-index", label: "No Refresh" },
32
+ { id: "changed-files-refresh", label: "Changed-Files Refresh" },
33
+ { id: "affected-neighborhood-refresh", label: "Affected-Neighborhood Refresh" },
34
+ { id: "full-refresh", label: "Full Refresh" }
35
+ ];
36
+ export const COMPARISON_PRESENTATION = [
37
+ { candidate: "stale-index", label: "No Refresh vs Full Refresh" },
38
+ { candidate: "changed-files-refresh", label: "Changed-Files Refresh vs Full Refresh" },
39
+ { candidate: "affected-neighborhood-refresh", label: "Affected-Neighborhood Refresh vs Full Refresh" }
40
+ ];
41
+ export function findReportTreatment(scenario, treatmentId) {
42
+ return scenario.treatments.find((treatment) => treatment.treatmentId === treatmentId) ?? null;
43
+ }
44
+ export function findReportComparison(scenario, candidateTreatmentId) {
45
+ return scenario.referenceComparisons.find((comparison) => comparison.candidateTreatmentId === candidateTreatmentId && comparison.referenceTreatmentId === "full-refresh") ?? null;
46
+ }
47
+ export function buildIncrementalChangeStalenessReportV2FromArtifact(artifact) {
48
+ for (const scenario of artifact.scenarios)
49
+ validateIncrementalChangeStalenessScenarioRecordV2(scenario);
50
+ return {
51
+ schemaVersion: INCREMENTAL_CHANGE_STALENESS_REPORT_SCHEMA_VERSION_V2,
52
+ pluginId: artifact.pluginId,
53
+ summary: { ...artifact.summary },
54
+ scenarios: artifact.scenarios.map(toReportScenario),
55
+ limitations: [...LIMITATIONS_V2]
56
+ };
57
+ }
58
+ function toReportScenario(record) {
59
+ return {
60
+ scenarioId: record.scenarioId,
61
+ category: record.category,
62
+ benchmarkProjectId: record.benchmarkProjectId,
63
+ baseCaseId: record.baseCaseId,
64
+ status: record.status,
65
+ failureReason: record.failureReason,
66
+ query: record.query,
67
+ answerPolicy: record.answerPolicy,
68
+ expectedFiles: [...record.expectedFiles],
69
+ expectedSymbols: [...record.expectedSymbols],
70
+ lifecycle: record.lifecycle ? structuredClone(record.lifecycle) : null,
71
+ // Persisted order is preserved; consumers resolve identity by id (see findReportTreatment/findReportComparison).
72
+ treatments: record.treatments.map((treatment) => structuredClone(treatment)),
73
+ referenceComparisons: record.referenceComparisons.map((comparison) => structuredClone(comparison))
74
+ };
75
+ }
76
+ /** Kept for callers that need the fixed id list without importing the plugin layer. */
77
+ export const REPORT_TREATMENT_IDS = INCREMENTAL_CHANGE_STALENESS_V2_TREATMENT_IDS;
78
+ export function isV2ReportSection(section) {
79
+ return section !== null && section.schemaVersion === INCREMENTAL_CHANGE_STALENESS_REPORT_SCHEMA_VERSION_V2;
80
+ }
@@ -1,6 +1,7 @@
1
1
  import path from "node:path";
2
2
  import { buildContextStrategyComparisonV043Report } from "./buildContextStrategyComparisonV043Report.js";
3
3
  import { buildWarmIndexReuseReport } from "./buildWarmIndexReuseReport.js";
4
+ import { buildIncrementalChangeStalenessPluginReport } from "./buildIncrementalChangeStalenessPluginReport.js";
4
5
  const V043_BULK_ARRAY_KEYS = [
5
6
  "v043StageContextExecutions",
6
7
  "v043StageContextEvaluations",
@@ -11,6 +12,7 @@ export function buildPluginExperimentReport(args) {
11
12
  const allOutcomes = args.run.cases.flatMap((experimentCase) => experimentCase.outcomes);
12
13
  const contextStrategyComparisonV043 = buildContextStrategyComparisonV043Report(args.run);
13
14
  const warmIndexReuse = buildWarmIndexReuseReport(args.run);
15
+ const incrementalChangeStaleness = buildIncrementalChangeStalenessPluginReport(args.run);
14
16
  const rawRun = { ...args.run, artifacts: relativizeArtifacts(args.run.artifacts, outputRoot) };
15
17
  for (const key of V043_BULK_ARRAY_KEYS) {
16
18
  delete rawRun[key];
@@ -39,6 +41,7 @@ export function buildPluginExperimentReport(args) {
39
41
  skippedOutcomes: allOutcomes.filter((outcome) => outcome.status === "skipped"),
40
42
  findings: buildFindings(args.run),
41
43
  warmIndexReuse,
44
+ incrementalChangeStaleness,
42
45
  contextStrategyComparisonV043,
43
46
  interpretation: buildInterpretation(args.run, contextStrategyComparisonV043, warmIndexReuse),
44
47
  rawRun,
@@ -0,0 +1,4 @@
1
+ // v0.6.2 Batch 5 -- plugin-specific presentation model types only. No projection logic, no HTML,
2
+ // no text rendering, and no recomputation live here; see buildIncrementalChangeStalenessReport.ts
3
+ // and renderIncrementalChangeStalenessHtml.ts for those responsibilities.
4
+ export const INCREMENTAL_CHANGE_STALENESS_REPORT_SCHEMA_VERSION = "my-dev-kit-lab-incremental-change-staleness-report-v1";
@@ -0,0 +1,3 @@
1
+ // v0.6.3 -- plugin-specific presentation model for the four-treatment V2 execution artifact. Types only:
2
+ // projection lives in buildIncrementalChangeStalenessReportV2.ts, rendering in the text/HTML renderers.
3
+ export const INCREMENTAL_CHANGE_STALENESS_REPORT_SCHEMA_VERSION_V2 = "my-dev-kit-lab-incremental-change-staleness-report-v2";
@@ -0,0 +1,146 @@
1
+ // v0.6.3 -- shared semantic content for the V2 text and HTML renderers, so both present exactly the same
2
+ // persisted facts. Formatting only: no classification, arithmetic, or recomputation. Unavailable evidence
3
+ // is written as words ("unavailable", "not applicable"), never as zero.
4
+ import { STALE_RISK_CLASSIFICATION_EXPLANATIONS } from "./buildIncrementalChangeStalenessReport.js";
5
+ import { COMPARISON_PRESENTATION, PARTIAL_REFERENCE_CLASSIFICATION_EXPLANATIONS, TREATMENT_PRESENTATION, findReportComparison, findReportTreatment } from "./buildIncrementalChangeStalenessReportV2.js";
6
+ import { REINDEX_RECOMMENDATION_EXPLANATIONS } from "./buildWarmIndexReuseReport.js";
7
+ export function display(value, missing = "unavailable") {
8
+ return value === null || value === undefined ? missing : String(value);
9
+ }
10
+ export function treatmentBlock(scenario, treatmentId, label) {
11
+ const treatment = findReportTreatment(scenario, treatmentId);
12
+ if (!treatment) {
13
+ return { label, available: false, rows: [], fallbackStatement: null, refreshRows: [], forcedNeighborSample: null, filesRead: [], requiredFiles: [], observedFiles: [], missingFiles: [] };
14
+ }
15
+ const retrieval = treatment.retrieval;
16
+ const correctness = treatment.fakeAgent?.correctness;
17
+ const refresh = treatment.refreshExecution;
18
+ const rows = [
19
+ ["Treatment status", treatment.status],
20
+ ["Treatment intent", treatment.treatmentIntent],
21
+ ["Failure reason", display(treatment.failureReason, "not applicable")],
22
+ ["Active index phase", treatment.activeIndexPhase],
23
+ ["Baseline freshness", treatment.baselineFreshness.status],
24
+ ["Refreshed freshness", display(treatment.refreshedFreshness?.status, "not applicable")],
25
+ ["Retrieval status", retrieval.status],
26
+ ["Context characters", retrieval.status === "not-run" ? "unavailable" : String(retrieval.totalChars)],
27
+ ["Estimated context tokens", retrieval.status === "not-run" ? "unavailable" : String(retrieval.totalEstimatedTokens)],
28
+ ["Retrieval duration (ms)", retrieval.status === "not-run" ? "unavailable" : String(retrieval.durationMs)],
29
+ ["Fake-agent status", display(treatment.fakeAgent?.status, "not run")],
30
+ ["Correctness score", correctness && correctness.available && correctness.score !== null ? String(correctness.score) : "unavailable"],
31
+ ["Required-file evidence", treatment.requiredFileEvidence.status],
32
+ ["Required-file reason", display(treatment.requiredFileEvidence.reason, "not applicable")],
33
+ ["Affected-neighborhood relationship", treatment.affectedNeighborhood.relationship],
34
+ ["Reindex recommendation (observational)", treatment.affectedNeighborhood.reindexRecommendation]
35
+ ];
36
+ const refreshRows = [
37
+ ["Refresh kind", refresh.kind],
38
+ ["Refresh realization", refresh.realization]
39
+ ];
40
+ let fallbackStatement = null;
41
+ let forcedNeighborSample = null;
42
+ if (refresh.kind === "incremental") {
43
+ const upstream = refresh.incrementalRefresh;
44
+ refreshRows.push(["Requested scope", upstream.requestedScope], ["Applied scope", upstream.appliedScope], ["Selection status", upstream.selectionStatus], ["Fallback reason", display(upstream.fallbackReason, "not applicable")], ["Fresh extraction files", String(upstream.freshExtractionFileCount)], ["Reused files", String(upstream.reusedFileCount)], ["Forced-neighbor reanalysis files", String(upstream.forcedNeighborReanalysisFileCount)], ["Upstream seed files", display(upstream.seedFileCount, "not recorded")], ["Upstream seed symbols", display(upstream.seedSymbolCount, "not recorded")], ["Upstream affected nodes", display(upstream.affectedNodeCount, "not recorded")], ["Upstream affected edges", display(upstream.affectedEdgeCount, "not recorded")]);
45
+ forcedNeighborSample = [...upstream.forcedNeighborSample];
46
+ if (refresh.realization === "FALLBACK_FULL") {
47
+ fallbackStatement = `FALLBACK_FULL: requested ${upstream.requestedScope}, applied ${upstream.appliedScope}, selection status ${upstream.selectionStatus}, reason ${display(upstream.fallbackReason, "not recorded")}. The requested partial refresh was not applied; a full rebuild produced this index.`;
48
+ }
49
+ }
50
+ else {
51
+ refreshRows.push(["Upstream incremental refresh evidence", "not applicable"]);
52
+ }
53
+ return {
54
+ label,
55
+ available: true,
56
+ rows,
57
+ fallbackStatement,
58
+ refreshRows,
59
+ forcedNeighborSample,
60
+ filesRead: [...retrieval.filesRead],
61
+ requiredFiles: [...treatment.requiredFileEvidence.requiredFiles],
62
+ observedFiles: [...treatment.requiredFileEvidence.observedFiles],
63
+ missingFiles: [...treatment.requiredFileEvidence.missingFiles]
64
+ };
65
+ }
66
+ export function comparisonBlock(scenario, presentation) {
67
+ const comparison = findReportComparison(scenario, presentation.candidate);
68
+ if (!comparison)
69
+ return { label: presentation.label, available: false, rows: [], reasonCodes: [], explanation: "" };
70
+ if (comparison.kind === "stale-risk") {
71
+ const value = comparison.comparison;
72
+ return {
73
+ label: presentation.label,
74
+ available: true,
75
+ rows: [
76
+ ["Correctness relation", value.correctnessRelation],
77
+ ["Required-file evidence relation", value.requiredFileEvidenceRelation],
78
+ ["Stale-risk classification", value.staleRiskClassification]
79
+ ],
80
+ reasonCodes: [...value.reasonCodes],
81
+ explanation: STALE_RISK_CLASSIFICATION_EXPLANATIONS[value.staleRiskClassification]
82
+ };
83
+ }
84
+ return {
85
+ label: presentation.label,
86
+ available: true,
87
+ rows: [
88
+ ["Refresh realization", display(comparison.refreshRealization, "not recorded")],
89
+ ["Candidate correctness relation", comparison.correctnessRelation],
90
+ ["Candidate required-file relation", comparison.requiredFileEvidenceRelation],
91
+ ["Reference classification", comparison.classification]
92
+ ],
93
+ reasonCodes: [...comparison.reasonCodes],
94
+ explanation: PARTIAL_REFERENCE_CLASSIFICATION_EXPLANATIONS[comparison.classification]
95
+ };
96
+ }
97
+ export const TREATMENT_PRESENTATION_ORDER = TREATMENT_PRESENTATION;
98
+ export const COMPARISON_PRESENTATION_ORDER = COMPARISON_PRESENTATION;
99
+ /**
100
+ * Baseline affected-neighborhood evidence is symmetric by construction of the execution; the report
101
+ * presents it once only when the persisted relationship and recommendation agree, and otherwise says so.
102
+ */
103
+ export function affectedNeighborhoodSummary(scenario) {
104
+ const assessments = TREATMENT_PRESENTATION.map((entry) => findReportTreatment(scenario, entry.id)?.affectedNeighborhood ?? null).filter((assessment) => assessment !== null);
105
+ if (assessments.length === 0)
106
+ return { state: "none" };
107
+ const first = assessments[0];
108
+ if (assessments.some((a) => a.relationship !== first.relationship || a.reindexRecommendation !== first.reindexRecommendation))
109
+ return { state: "inconsistent" };
110
+ const number = (value) => display(value);
111
+ return {
112
+ state: "ok",
113
+ rows: [
114
+ ["Relationship", first.relationship],
115
+ ["Reindex recommendation (observational)", first.reindexRecommendation],
116
+ ["Changed indexed files", number(first.changedFileCount)],
117
+ ["Changed baseline symbols", number(first.changedSymbolCount)],
118
+ ["Affected graph nodes", number(first.affectedNodeCount)],
119
+ ["Affected graph edges", number(first.affectedEdgeCount)],
120
+ ["Task-overlap nodes", number(first.taskOverlapCount)],
121
+ ["Task-overlap percent", number(first.taskOverlapPercent)]
122
+ ],
123
+ explanation: REINDEX_RECOMMENDATION_EXPLANATIONS[first.reindexRecommendation]
124
+ };
125
+ }
126
+ export const EVIDENCE_FAMILY_NOTE = "Lab affected-neighborhood evidence describes the baseline graph neighborhood implicated by the controlled change. Upstream refresh evidence describes what my-dev-kit actually refreshed. They are different evidence families.";
127
+ export const SCENARIO_SUMMARY_ROWS = (summary) => [
128
+ ["Scenarios", String(summary.scenarioCount)],
129
+ ["Ready", String(summary.readyScenarioCount)],
130
+ ["Failed", String(summary.failedScenarioCount)],
131
+ ["No refresh: observed stale regression", String(summary.staleObservedRegressionCount)],
132
+ ["No refresh: no observed stale regression", String(summary.staleNoObservedRegressionCount)],
133
+ ["No refresh: inconclusive", String(summary.staleInconclusiveCount)],
134
+ ["Changed-files: applied partial", String(summary.changedFilesAppliedPartialCount)],
135
+ ["Changed-files: fallback to full", String(summary.changedFilesFallbackFullCount)],
136
+ ["Affected-neighborhood: applied partial", String(summary.affectedNeighborhoodAppliedPartialCount)],
137
+ ["Affected-neighborhood: fallback to full", String(summary.affectedNeighborhoodFallbackFullCount)],
138
+ ["Changed-files: observed regression relative to full", String(summary.changedFilesObservedRegressionRelativeToFullCount)],
139
+ ["Changed-files: no observed regression relative to full", String(summary.changedFilesNoObservedRegressionRelativeToFullCount)],
140
+ ["Changed-files: inconclusive", String(summary.changedFilesInconclusiveCount)],
141
+ ["Changed-files: not comparable as partial refresh", String(summary.changedFilesNotComparableCount)],
142
+ ["Affected-neighborhood: observed regression relative to full", String(summary.affectedNeighborhoodObservedRegressionRelativeToFullCount)],
143
+ ["Affected-neighborhood: no observed regression relative to full", String(summary.affectedNeighborhoodNoObservedRegressionRelativeToFullCount)],
144
+ ["Affected-neighborhood: inconclusive", String(summary.affectedNeighborhoodInconclusiveCount)],
145
+ ["Affected-neighborhood: not comparable as partial refresh", String(summary.affectedNeighborhoodNotComparableCount)]
146
+ ];
@@ -14,3 +14,9 @@ export { buildContextIntegrityReport } from "./buildContextIntegrityReport.js";
14
14
  export { renderContextIntegrityJsonReport } from "./renderContextIntegrityJsonReport.js";
15
15
  export { renderContextIntegrityText } from "./renderContextIntegrityText.js";
16
16
  export { renderContextIntegrityHtml } from "./renderContextIntegrityHtml.js";
17
+ export * from "./incrementalChangeStalenessReportModel.js";
18
+ export * from "./incrementalChangeStalenessReportModelV2.js";
19
+ export { buildIncrementalChangeStalenessPluginReport } from "./buildIncrementalChangeStalenessPluginReport.js";
20
+ export { buildIncrementalChangeStalenessReportV2FromArtifact, findReportTreatment, findReportComparison, LIMITATIONS_V2 } from "./buildIncrementalChangeStalenessReportV2.js";
21
+ export { buildIncrementalChangeStalenessReport, STALE_RISK_CLASSIFICATION_EXPLANATIONS } from "./buildIncrementalChangeStalenessReport.js";
22
+ export { renderIncrementalChangeStalenessHtml } from "./renderIncrementalChangeStalenessHtml.js";