@dailephd/my-dev-kit-lab 0.6.0 → 0.6.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 +32 -0
- package/README.md +7 -4
- package/benchmarks/contracts/incremental-change-staleness-scenarios.json +360 -0
- package/dist/src/commands/runExperimentRunCommand.js +4 -3
- package/dist/src/evaluation/affectedNeighborhood.js +712 -0
- package/dist/src/evaluation/affectedNeighborhoodMetrics.js +51 -0
- package/dist/src/evaluation/indexSnapshot.js +1 -1
- package/dist/src/experiments/defaultRegistry.js +2 -0
- package/dist/src/experiments/plugins/incrementalChangeStaleness/comparison.js +101 -0
- package/dist/src/experiments/plugins/incrementalChangeStaleness/config.js +66 -0
- package/dist/src/experiments/plugins/incrementalChangeStaleness/disposableTarget.js +107 -0
- package/dist/src/experiments/plugins/incrementalChangeStaleness/execution.js +404 -0
- package/dist/src/experiments/plugins/incrementalChangeStaleness/executionArtifact.js +160 -0
- package/dist/src/experiments/plugins/incrementalChangeStaleness/index.js +13 -0
- package/dist/src/experiments/plugins/incrementalChangeStaleness/lifecycle.js +276 -0
- package/dist/src/experiments/plugins/incrementalChangeStaleness/lifecyclePolicy.js +152 -0
- package/dist/src/experiments/plugins/incrementalChangeStaleness/mutationExecution.js +144 -0
- package/dist/src/experiments/plugins/incrementalChangeStaleness/plugin.js +274 -0
- package/dist/src/experiments/plugins/incrementalChangeStaleness/scenarioCatalog.js +42 -0
- package/dist/src/experiments/plugins/incrementalChangeStaleness/scenarioMutation.js +116 -0
- package/dist/src/experiments/plugins/incrementalChangeStaleness/scenarioSelection.js +79 -0
- package/dist/src/experiments/plugins/incrementalChangeStaleness/scenarioTypes.js +37 -0
- package/dist/src/experiments/plugins/incrementalChangeStaleness/scenarioValidation.js +168 -0
- package/dist/src/experiments/plugins/incrementalChangeStaleness/sourceState.js +145 -0
- package/dist/src/experiments/plugins/incrementalChangeStaleness/treatmentSession.js +6 -0
- package/dist/src/experiments/plugins/index.js +1 -0
- package/dist/src/experiments/plugins/warmIndexReuse/execution.js +21 -0
- package/dist/src/experiments/plugins/warmIndexReuse/executionArtifact.js +1 -0
- package/dist/src/experiments/plugins/warmIndexReuse/metrics.js +68 -0
- package/dist/src/experiments/plugins/warmIndexReuse/warmIndexSession.js +9 -1
- package/dist/src/report/experiments/buildIncrementalChangeStalenessReport.js +112 -0
- package/dist/src/report/experiments/buildPluginExperimentReport.js +17 -2
- package/dist/src/report/experiments/buildWarmIndexReuseReport.js +80 -3
- package/dist/src/report/experiments/incrementalChangeStalenessReportModel.js +4 -0
- package/dist/src/report/experiments/index.js +3 -0
- package/dist/src/report/experiments/renderIncrementalChangeStalenessHtml.js +239 -0
- package/dist/src/report/experiments/renderPluginExperimentReportHtml.js +3 -0
- package/dist/src/report/experiments/renderPluginExperimentReportText.js +222 -0
- package/dist/src/report/experiments/renderWarmIndexReuseHtml.js +98 -0
- package/dist/src/report/experiments/warmIndexReuseReportModel.js +5 -0
- package/docs/METRICS.md +79 -3
- package/examples/tutorial-browser/prepare.mjs +13 -13
- package/examples/tutorial-browser/server.mjs +13 -13
- package/package.json +1 -1
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { STALE_RISK_CLASSIFICATION_EXPLANATIONS } from "./buildIncrementalChangeStalenessReport.js";
|
|
2
|
+
import { REINDEX_RECOMMENDATION_EXPLANATIONS } from "./buildWarmIndexReuseReport.js";
|
|
1
3
|
function sanitizeScalar(value) {
|
|
2
4
|
const text = String(value);
|
|
3
5
|
let result = "";
|
|
@@ -302,6 +304,7 @@ function renderWarmIndexReuseSection(lines, section) {
|
|
|
302
304
|
lines.push(fieldLine("Agent Correctness Available (task sides)", `${section.summary.agentCorrectnessAvailableCount} of ${section.summary.agentSideCount}`));
|
|
303
305
|
lines.push(fieldLine("Agent Token Totals Available (task sides)", `${section.summary.agentTotalTokensAvailableCount} of ${section.summary.agentSideCount}`));
|
|
304
306
|
renderFreshnessSummary(lines, section);
|
|
307
|
+
renderAffectedNeighborhoodSummary(lines, section);
|
|
305
308
|
if (section.agentCampaign) {
|
|
306
309
|
renderCampaignSection(lines, section.agentCampaign);
|
|
307
310
|
}
|
|
@@ -326,6 +329,7 @@ function renderWarmIndexReuseSection(lines, section) {
|
|
|
326
329
|
["Raw Status", task.rawStatus],
|
|
327
330
|
["Warm Status", task.warmStatus],
|
|
328
331
|
["Index Freshness Status", task.indexFreshness ? task.indexFreshness.status : "not assessed"],
|
|
332
|
+
["Affected Neighborhood Status", task.affectedNeighborhood ? task.affectedNeighborhood.status : "not assessed"],
|
|
329
333
|
["Raw Context Characters", formatWarmMetric(task.raw.contextCharacters)],
|
|
330
334
|
["Warm Context Characters", formatWarmMetric(task.warm.contextCharacters)],
|
|
331
335
|
["Raw Estimated Context Tokens (estimate)", formatWarmMetric(task.raw.contextEstimatedTokens)],
|
|
@@ -350,6 +354,7 @@ function renderWarmIndexReuseSection(lines, section) {
|
|
|
350
354
|
lines.push(fieldLine(label, value));
|
|
351
355
|
}
|
|
352
356
|
renderTaskFreshnessDetail(lines, task.indexFreshness);
|
|
357
|
+
renderTaskAffectedNeighborhoodDetail(lines, task.affectedNeighborhood);
|
|
353
358
|
}
|
|
354
359
|
});
|
|
355
360
|
}
|
|
@@ -371,6 +376,87 @@ function renderFreshnessSummary(lines, section) {
|
|
|
371
376
|
lines.push(fieldLine("Partially-Stale Tasks", summary.partiallyStaleTaskCount));
|
|
372
377
|
lines.push(fieldLine("Unknown Tasks", summary.unknownTaskCount));
|
|
373
378
|
}
|
|
379
|
+
function renderAffectedNeighborhoodSummary(lines, section) {
|
|
380
|
+
// Reports serialized before v0.6.1 lack the summary; render them as having no assessment.
|
|
381
|
+
const summary = section.affectedNeighborhoodSummary ?? {
|
|
382
|
+
assessedTaskCount: 0,
|
|
383
|
+
unassessedTaskCount: section.summary.taskCount,
|
|
384
|
+
completeAssessmentCount: 0,
|
|
385
|
+
partialAssessmentCount: 0,
|
|
386
|
+
unavailableAssessmentCount: 0,
|
|
387
|
+
relatedTaskCount: 0,
|
|
388
|
+
unrelatedTaskCount: 0,
|
|
389
|
+
unknownRelationshipTaskCount: 0,
|
|
390
|
+
recommendedReindexCount: 0,
|
|
391
|
+
notIndicatedReindexCount: 0,
|
|
392
|
+
unknownReindexRecommendationCount: 0,
|
|
393
|
+
};
|
|
394
|
+
pushSection(lines, "Affected Neighborhood Summary");
|
|
395
|
+
lines.push(fieldLine("Assessed Tasks", summary.assessedTaskCount));
|
|
396
|
+
lines.push(fieldLine("Unassessed Tasks", summary.unassessedTaskCount));
|
|
397
|
+
lines.push(fieldLine("Complete Assessments", summary.completeAssessmentCount));
|
|
398
|
+
lines.push(fieldLine("Partial Assessments", summary.partialAssessmentCount));
|
|
399
|
+
lines.push(fieldLine("Unavailable Assessments", summary.unavailableAssessmentCount));
|
|
400
|
+
lines.push(fieldLine("Related Tasks", summary.relatedTaskCount));
|
|
401
|
+
lines.push(fieldLine("Unrelated Tasks", summary.unrelatedTaskCount));
|
|
402
|
+
lines.push(fieldLine("Unknown-Relationship Tasks", summary.unknownRelationshipTaskCount));
|
|
403
|
+
lines.push(fieldLine("Reindex Recommended", summary.recommendedReindexCount));
|
|
404
|
+
lines.push(fieldLine("Reindex Not Indicated", summary.notIndicatedReindexCount));
|
|
405
|
+
lines.push(fieldLine("Reindex Recommendation Unknown", summary.unknownReindexRecommendationCount));
|
|
406
|
+
}
|
|
407
|
+
/** Renders the persisted affected-neighborhood evidence; numeric lines come from the metric owner. */
|
|
408
|
+
function renderTaskAffectedNeighborhoodDetail(lines, evidence) {
|
|
409
|
+
if (!evidence)
|
|
410
|
+
return;
|
|
411
|
+
lines.push(fieldLine("Affected Neighborhood Relationship", evidence.relationship));
|
|
412
|
+
lines.push(fieldLine("Reindex Recommendation", evidence.reindexRecommendation));
|
|
413
|
+
lines.push(fieldLine("Reindex Recommendation Explanation", evidence.recommendationExplanation));
|
|
414
|
+
lines.push(fieldLine("Affected Neighborhood Freshness Status", evidence.freshnessStatus));
|
|
415
|
+
lines.push(fieldLine("Seed Mapping Status", evidence.seedMappingStatus));
|
|
416
|
+
lines.push(fieldLine("Graph Evidence Status", evidence.graphEvidenceStatus));
|
|
417
|
+
lines.push(fieldLine("Neighborhood Status", evidence.neighborhoodStatus));
|
|
418
|
+
lines.push(fieldLine("Task Mapping Status", evidence.taskMappingStatus));
|
|
419
|
+
lines.push(fieldLine("Resolvable Task Nodes", evidence.resolvableTaskNodeCount));
|
|
420
|
+
lines.push(fieldLine("Changed Indexed Files", formatWarmMetric(evidence.metrics.changedFileCount)));
|
|
421
|
+
lines.push(fieldLine("Changed Baseline Symbols", formatWarmMetric(evidence.metrics.changedSymbolCount)));
|
|
422
|
+
lines.push(fieldLine("Affected Graph Nodes", formatWarmMetric(evidence.metrics.affectedNodeCount)));
|
|
423
|
+
lines.push(fieldLine("Affected Graph Edges", formatWarmMetric(evidence.metrics.affectedEdgeCount)));
|
|
424
|
+
lines.push(fieldLine("Task-Overlap Nodes", formatWarmMetric(evidence.metrics.taskOverlapCount)));
|
|
425
|
+
lines.push(fieldLine("Task-Overlap Percent", formatWarmMetric(evidence.metrics.taskOverlapPercent)));
|
|
426
|
+
lines.push("Affected Node IDs:");
|
|
427
|
+
pushFreshnessBoundedNote(lines, evidence.affectedNodeIds);
|
|
428
|
+
if (evidence.affectedNodeIds.items.length === 0)
|
|
429
|
+
lines.push("- none");
|
|
430
|
+
for (const id of evidence.affectedNodeIds.items)
|
|
431
|
+
lines.push(`- ${sanitizeScalar(id)}`);
|
|
432
|
+
lines.push("Participating Edge IDs:");
|
|
433
|
+
pushFreshnessBoundedNote(lines, evidence.participatingEdgeIds);
|
|
434
|
+
if (evidence.participatingEdgeIds.items.length === 0)
|
|
435
|
+
lines.push("- none");
|
|
436
|
+
for (const id of evidence.participatingEdgeIds.items)
|
|
437
|
+
lines.push(`- ${sanitizeScalar(id)}`);
|
|
438
|
+
lines.push("Unresolved Task Mappings:");
|
|
439
|
+
pushFreshnessBoundedNote(lines, evidence.unresolvedTaskMappings);
|
|
440
|
+
if (evidence.unresolvedTaskMappings.items.length === 0)
|
|
441
|
+
lines.push("- none");
|
|
442
|
+
for (const entry of evidence.unresolvedTaskMappings.items) {
|
|
443
|
+
lines.push(`- ${sanitizeScalar(entry.subject)} ${sanitizeScalar(entry.name)} [${sanitizeScalar(entry.reason)}]`);
|
|
444
|
+
}
|
|
445
|
+
lines.push("Ambiguous Task Symbols:");
|
|
446
|
+
pushFreshnessBoundedNote(lines, evidence.ambiguousTaskSymbols);
|
|
447
|
+
if (evidence.ambiguousTaskSymbols.items.length === 0)
|
|
448
|
+
lines.push("- none");
|
|
449
|
+
for (const entry of evidence.ambiguousTaskSymbols.items) {
|
|
450
|
+
lines.push(`- ${sanitizeScalar(entry.name)}: ${entry.candidateNodeIds.map((id) => sanitizeScalar(id)).join(", ")}`);
|
|
451
|
+
}
|
|
452
|
+
lines.push("Affected Neighborhood Warnings:");
|
|
453
|
+
pushFreshnessBoundedNote(lines, evidence.warnings);
|
|
454
|
+
if (evidence.warnings.items.length === 0)
|
|
455
|
+
lines.push("- none");
|
|
456
|
+
for (const warning of evidence.warnings.items) {
|
|
457
|
+
lines.push(`- [${sanitizeScalar(warning.code)}] ${sanitizeScalar(warning.message)}`);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
374
460
|
function pushFreshnessBoundedNote(lines, list) {
|
|
375
461
|
if (list.omittedCount > 0) {
|
|
376
462
|
lines.push(`Displayed: ${list.displayedCount} of ${list.totalCount}`);
|
|
@@ -405,6 +491,140 @@ function renderTaskFreshnessDetail(lines, freshness) {
|
|
|
405
491
|
lines.push(`- ${entry.path === null ? "run-level" : sanitizeScalar(entry.path)} [${sanitizeScalar(entry.reasonCode)}] ${sanitizeScalar(entry.message)}`);
|
|
406
492
|
}
|
|
407
493
|
}
|
|
494
|
+
const INCREMENTAL_CHANGE_STALENESS_MAX_DISPLAY_ITEMS = 20;
|
|
495
|
+
function pushBoundedStringList(lines, label, items) {
|
|
496
|
+
lines.push(`${label}:`);
|
|
497
|
+
if (items.length === 0) {
|
|
498
|
+
lines.push("- none");
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
const shown = items.slice(0, INCREMENTAL_CHANGE_STALENESS_MAX_DISPLAY_ITEMS);
|
|
502
|
+
for (const item of shown)
|
|
503
|
+
lines.push(`- ${sanitizeScalar(item)}`);
|
|
504
|
+
const omitted = items.length - shown.length;
|
|
505
|
+
if (omitted > 0)
|
|
506
|
+
lines.push(`... ${omitted} more`);
|
|
507
|
+
}
|
|
508
|
+
function symmetricIncrementalAffectedNeighborhood(scenario) {
|
|
509
|
+
const stale = scenario.staleTreatment?.affectedNeighborhood ?? null;
|
|
510
|
+
const fullRefresh = scenario.fullRefreshTreatment?.affectedNeighborhood ?? null;
|
|
511
|
+
if (!stale || !fullRefresh)
|
|
512
|
+
return null;
|
|
513
|
+
if (stale.relationship !== fullRefresh.relationship || stale.reindexRecommendation !== fullRefresh.reindexRecommendation) {
|
|
514
|
+
return null;
|
|
515
|
+
}
|
|
516
|
+
return stale;
|
|
517
|
+
}
|
|
518
|
+
function renderIncrementalChangeStalenessTreatment(lines, label, treatment) {
|
|
519
|
+
pushSection(lines, label);
|
|
520
|
+
if (!treatment) {
|
|
521
|
+
lines.push("Not available.");
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
lines.push(fieldLine("Treatment Status", treatment.status));
|
|
525
|
+
lines.push(fieldLine("Failure Reason", treatment.failureReason));
|
|
526
|
+
lines.push(fieldLine("Active Index Phase", treatment.activeIndexPhase));
|
|
527
|
+
lines.push(fieldLine("Baseline Freshness Status", treatment.baselineFreshness.status));
|
|
528
|
+
lines.push("Retrieval (deterministic fake-agent / simulated harness evidence label applies to fakeAgent below):");
|
|
529
|
+
lines.push(fieldLine("Retrieval Status", treatment.retrieval.status));
|
|
530
|
+
lines.push(fieldLine("Selected Node ID", treatment.retrieval.selectedNodeId));
|
|
531
|
+
lines.push(fieldLine("Selected File", treatment.retrieval.selectedFile));
|
|
532
|
+
lines.push(fieldLine("Selected Symbol", treatment.retrieval.selectedSymbol));
|
|
533
|
+
lines.push(fieldLine("Context Characters", treatment.retrieval.totalChars));
|
|
534
|
+
lines.push(fieldLine("Estimated Context Tokens", treatment.retrieval.totalEstimatedTokens));
|
|
535
|
+
lines.push(fieldLine("Retrieval Duration (ms)", treatment.retrieval.durationMs));
|
|
536
|
+
pushBoundedStringList(lines, "Files Read", treatment.retrieval.filesRead);
|
|
537
|
+
pushBoundedStringList(lines, "Retrieval Warnings", treatment.retrieval.warnings);
|
|
538
|
+
lines.push("Fake-Agent Evidence (deterministic fake-agent / simulated harness evidence):");
|
|
539
|
+
if (treatment.fakeAgent) {
|
|
540
|
+
lines.push(fieldLine("Fake-Agent Status", treatment.fakeAgent.status));
|
|
541
|
+
lines.push(fieldLine("Correctness Score", treatment.fakeAgent.correctness.available ? treatment.fakeAgent.correctness.score : null));
|
|
542
|
+
lines.push(fieldLine("Passed", treatment.fakeAgent.correctness.passed));
|
|
543
|
+
lines.push(fieldLine("Fake-Agent Duration (ms)", treatment.fakeAgent.durationMs));
|
|
544
|
+
}
|
|
545
|
+
else {
|
|
546
|
+
lines.push("Not run for this treatment.");
|
|
547
|
+
}
|
|
548
|
+
lines.push("Required-File Evidence (bounded answer-key file-presence check, not retrieval precision/recall):");
|
|
549
|
+
lines.push(fieldLine("Required-File Status", treatment.requiredFileEvidence.status));
|
|
550
|
+
lines.push(fieldLine("Required-File Reason", treatment.requiredFileEvidence.reason));
|
|
551
|
+
pushBoundedStringList(lines, "Required Files", treatment.requiredFileEvidence.requiredFiles);
|
|
552
|
+
pushBoundedStringList(lines, "Observed Files", treatment.requiredFileEvidence.observedFiles);
|
|
553
|
+
pushBoundedStringList(lines, "Missing Files", treatment.requiredFileEvidence.missingFiles);
|
|
554
|
+
}
|
|
555
|
+
function renderIncrementalChangeStalenessScenario(lines, scenario) {
|
|
556
|
+
pushSection(lines, `Scenario: ${scenario.scenarioId}`);
|
|
557
|
+
lines.push(fieldLine("Category", scenario.category));
|
|
558
|
+
lines.push(fieldLine("Status", scenario.status));
|
|
559
|
+
lines.push(fieldLine("Failure Reason", scenario.failureReason));
|
|
560
|
+
lines.push(fieldLine("Benchmark Project", scenario.benchmarkProjectId));
|
|
561
|
+
lines.push(fieldLine("Base Case", scenario.baseCaseId));
|
|
562
|
+
lines.push(fieldLine("Answer Policy", scenario.answerPolicy));
|
|
563
|
+
lines.push(fieldLine("Query", scenario.query));
|
|
564
|
+
pushBoundedStringList(lines, "Expected Files", scenario.expectedFiles);
|
|
565
|
+
pushBoundedStringList(lines, "Expected Symbols", scenario.expectedSymbols);
|
|
566
|
+
pushSection(lines, "Lifecycle");
|
|
567
|
+
const lifecycle = scenario.lifecycle;
|
|
568
|
+
if (!lifecycle) {
|
|
569
|
+
lines.push("Not available.");
|
|
570
|
+
}
|
|
571
|
+
else {
|
|
572
|
+
lines.push(fieldLine("Pre-Mutation Equivalence", lifecycle.preMutationEquivalence));
|
|
573
|
+
lines.push(fieldLine("Post-Mutation Equivalence", lifecycle.postMutationEquivalence));
|
|
574
|
+
lines.push(fieldLine("my-dev-kit Version", lifecycle.myDevKitVersion));
|
|
575
|
+
lines.push(fieldLine("Stale Baseline Freshness Status", lifecycle.staleBaselineFreshnessStatus));
|
|
576
|
+
lines.push(fieldLine("Full-Refresh Baseline Freshness Status", lifecycle.fullRefreshBaselineFreshnessStatus));
|
|
577
|
+
lines.push(fieldLine("Full-Refresh Refreshed Freshness Status", lifecycle.fullRefreshRefreshedFreshnessStatus));
|
|
578
|
+
pushBoundedStringList(lines, "Controlled File Paths", lifecycle.controlledFilePaths);
|
|
579
|
+
pushBoundedStringList(lines, "Source Roots", lifecycle.sourceRoots);
|
|
580
|
+
}
|
|
581
|
+
pushSection(lines, "Affected-Neighborhood Evidence (changed baseline neighborhood relative to the task)");
|
|
582
|
+
const symmetric = symmetricIncrementalAffectedNeighborhood(scenario);
|
|
583
|
+
const bothPresent = scenario.staleTreatment?.affectedNeighborhood && scenario.fullRefreshTreatment?.affectedNeighborhood;
|
|
584
|
+
if (!scenario.staleTreatment?.affectedNeighborhood && !scenario.fullRefreshTreatment?.affectedNeighborhood) {
|
|
585
|
+
lines.push("Not available.");
|
|
586
|
+
}
|
|
587
|
+
else if (bothPresent && !symmetric) {
|
|
588
|
+
lines.push("inconsistent persisted evidence");
|
|
589
|
+
}
|
|
590
|
+
else if (symmetric) {
|
|
591
|
+
lines.push(fieldLine("Relationship", symmetric.relationship));
|
|
592
|
+
lines.push(fieldLine("Reindex Recommendation", symmetric.reindexRecommendation));
|
|
593
|
+
lines.push(fieldLine("Reindex Recommendation Explanation", REINDEX_RECOMMENDATION_EXPLANATIONS[symmetric.reindexRecommendation]));
|
|
594
|
+
lines.push(fieldLine("Changed Indexed Files", symmetric.changedFileCount));
|
|
595
|
+
lines.push(fieldLine("Changed Baseline Symbols", symmetric.changedSymbolCount));
|
|
596
|
+
lines.push(fieldLine("Affected Graph Nodes", symmetric.affectedNodeCount));
|
|
597
|
+
lines.push(fieldLine("Affected Graph Edges", symmetric.affectedEdgeCount));
|
|
598
|
+
lines.push(fieldLine("Task-Overlap Nodes", symmetric.taskOverlapCount));
|
|
599
|
+
lines.push(fieldLine("Task-Overlap Percent", symmetric.taskOverlapPercent));
|
|
600
|
+
}
|
|
601
|
+
renderIncrementalChangeStalenessTreatment(lines, "Stale-Index Treatment", scenario.staleTreatment);
|
|
602
|
+
renderIncrementalChangeStalenessTreatment(lines, "Full-Refresh Treatment", scenario.fullRefreshTreatment);
|
|
603
|
+
pushSection(lines, "Matched Comparison");
|
|
604
|
+
lines.push(fieldLine("Correctness Relation", scenario.comparison.correctnessRelation));
|
|
605
|
+
lines.push(fieldLine("Required-File Evidence Relation", scenario.comparison.requiredFileEvidenceRelation));
|
|
606
|
+
lines.push(fieldLine("Stale-Risk Classification", scenario.comparison.staleRiskClassification));
|
|
607
|
+
pushDashList(lines, scenario.comparison.reasonCodes);
|
|
608
|
+
lines.push(STALE_RISK_CLASSIFICATION_EXPLANATIONS[scenario.comparison.staleRiskClassification]);
|
|
609
|
+
}
|
|
610
|
+
function renderIncrementalChangeStalenessSection(lines, section) {
|
|
611
|
+
if (section === null) {
|
|
612
|
+
lines.push("Not applicable to this plugin.");
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
pushSection(lines, "Summary");
|
|
616
|
+
lines.push(fieldLine("Scenarios", section.scenarioCount));
|
|
617
|
+
lines.push(fieldLine("Ready", section.readyScenarioCount));
|
|
618
|
+
lines.push(fieldLine("Failed", section.failedScenarioCount));
|
|
619
|
+
lines.push(fieldLine("Observed Stale Regression", section.observedStaleRegressionCount));
|
|
620
|
+
lines.push(fieldLine("No Observed Stale Regression", section.noObservedStaleRegressionCount));
|
|
621
|
+
lines.push(fieldLine("Inconclusive", section.inconclusiveCount));
|
|
622
|
+
for (const scenario of section.scenarios) {
|
|
623
|
+
renderIncrementalChangeStalenessScenario(lines, scenario);
|
|
624
|
+
}
|
|
625
|
+
pushSection(lines, "Limitations");
|
|
626
|
+
pushDashList(lines, section.limitations);
|
|
627
|
+
}
|
|
408
628
|
export function renderPluginExperimentReportText(report) {
|
|
409
629
|
const lines = [];
|
|
410
630
|
pushSection(lines, "Plugin Experiment Report");
|
|
@@ -435,6 +655,8 @@ export function renderPluginExperimentReportText(report) {
|
|
|
435
655
|
renderV043Section(lines, report.contextStrategyComparisonV043);
|
|
436
656
|
pushSection(lines, "Warm Index Reuse Evidence");
|
|
437
657
|
renderWarmIndexReuseSection(lines, report.warmIndexReuse);
|
|
658
|
+
pushSection(lines, "Incremental-Change And Staleness Evidence");
|
|
659
|
+
renderIncrementalChangeStalenessSection(lines, report.incrementalChangeStaleness);
|
|
438
660
|
pushSection(lines, "Warnings, Skips, And Failures");
|
|
439
661
|
pushDashList(lines, report.findings.map((finding) => `${finding.severity} ${finding.code} ${finding.message} variant=${finding.variantId ?? ""} case=${finding.caseId ?? ""}`));
|
|
440
662
|
pushSection(lines, "Artifacts");
|
|
@@ -5,6 +5,8 @@ const UNIT_LABELS = {
|
|
|
5
5
|
"estimated-tokens": "est. tokens",
|
|
6
6
|
tokens: "tokens",
|
|
7
7
|
score: "score",
|
|
8
|
+
count: "",
|
|
9
|
+
percent: "%",
|
|
8
10
|
};
|
|
9
11
|
export function renderWarmIndexReuseHtml(section) {
|
|
10
12
|
if (section === null) {
|
|
@@ -26,6 +28,7 @@ export function renderWarmIndexReuseHtml(section) {
|
|
|
26
28
|
["Agent token totals available", `${section.summary.agentTotalTokensAvailableCount} of ${section.summary.agentSideCount}`],
|
|
27
29
|
])}
|
|
28
30
|
${renderFreshnessSummary(section.indexFreshnessSummary ?? legacyFreshnessSummary(section.summary.taskCount))}
|
|
31
|
+
${renderAffectedNeighborhoodSummary(section.affectedNeighborhoodSummary ?? legacyAffectedNeighborhoodSummary(section.summary.taskCount))}
|
|
29
32
|
${section.agentCampaign ? renderCampaignSection(section.agentCampaign) : ""}
|
|
30
33
|
<h3>Cold Start And Warm Reuse</h3>
|
|
31
34
|
${list(section.costModel)}
|
|
@@ -45,6 +48,100 @@ function legacyFreshnessSummary(taskCount) {
|
|
|
45
48
|
unknownTaskCount: 0,
|
|
46
49
|
};
|
|
47
50
|
}
|
|
51
|
+
// Reports serialized before v0.6.1 lack the summary; they are rendered as having no assessment.
|
|
52
|
+
function legacyAffectedNeighborhoodSummary(taskCount) {
|
|
53
|
+
return {
|
|
54
|
+
assessedTaskCount: 0,
|
|
55
|
+
unassessedTaskCount: taskCount,
|
|
56
|
+
completeAssessmentCount: 0,
|
|
57
|
+
partialAssessmentCount: 0,
|
|
58
|
+
unavailableAssessmentCount: 0,
|
|
59
|
+
relatedTaskCount: 0,
|
|
60
|
+
unrelatedTaskCount: 0,
|
|
61
|
+
unknownRelationshipTaskCount: 0,
|
|
62
|
+
recommendedReindexCount: 0,
|
|
63
|
+
notIndicatedReindexCount: 0,
|
|
64
|
+
unknownReindexRecommendationCount: 0,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function renderAffectedNeighborhoodSummary(summary) {
|
|
68
|
+
return `<h3>Affected Neighborhood</h3>
|
|
69
|
+
${table(["Field", "Value"], [
|
|
70
|
+
["Assessed tasks", String(summary.assessedTaskCount)],
|
|
71
|
+
["Unassessed tasks", String(summary.unassessedTaskCount)],
|
|
72
|
+
["Complete assessments", String(summary.completeAssessmentCount)],
|
|
73
|
+
["Partial assessments", String(summary.partialAssessmentCount)],
|
|
74
|
+
["Unavailable assessments", String(summary.unavailableAssessmentCount)],
|
|
75
|
+
["Related tasks", String(summary.relatedTaskCount)],
|
|
76
|
+
["Unrelated tasks", String(summary.unrelatedTaskCount)],
|
|
77
|
+
["Unknown-relationship tasks", String(summary.unknownRelationshipTaskCount)],
|
|
78
|
+
["Reindex recommended", String(summary.recommendedReindexCount)],
|
|
79
|
+
["Reindex not indicated", String(summary.notIndicatedReindexCount)],
|
|
80
|
+
["Reindex recommendation unknown", String(summary.unknownReindexRecommendationCount)],
|
|
81
|
+
])}`;
|
|
82
|
+
}
|
|
83
|
+
function boundedNote(label, taskOrdinal, bounded) {
|
|
84
|
+
return bounded.omittedCount > 0 ? [`Task ${taskOrdinal}: ${bounded.omittedCount} of ${bounded.totalCount} ${label} omitted.`] : [];
|
|
85
|
+
}
|
|
86
|
+
/** Persisted affected-neighborhood evidence per task; numeric cells are the metric owner's objects. */
|
|
87
|
+
function renderProjectAffectedNeighborhood(tasks) {
|
|
88
|
+
const overview = table(["Task", "Assessment", "Relationship", "Reindex recommendation", "Freshness", "Seeds", "Neighborhood", "Task mapping", "Resolvable task nodes"], tasks.map((task) => {
|
|
89
|
+
const label = `${task.taskOrdinal}. ${task.caseId}`;
|
|
90
|
+
const evidence = task.affectedNeighborhood;
|
|
91
|
+
return evidence
|
|
92
|
+
? [
|
|
93
|
+
label,
|
|
94
|
+
evidence.status,
|
|
95
|
+
evidence.relationship,
|
|
96
|
+
evidence.reindexRecommendation,
|
|
97
|
+
evidence.freshnessStatus,
|
|
98
|
+
evidence.seedMappingStatus,
|
|
99
|
+
evidence.neighborhoodStatus,
|
|
100
|
+
evidence.taskMappingStatus,
|
|
101
|
+
String(evidence.resolvableTaskNodeCount),
|
|
102
|
+
]
|
|
103
|
+
: [label, "not assessed", "", "", "", "", "", "", ""];
|
|
104
|
+
}));
|
|
105
|
+
const assessed = tasks.filter((task) => task.affectedNeighborhood !== null);
|
|
106
|
+
const metricRows = assessed.map((task) => {
|
|
107
|
+
const metrics = task.affectedNeighborhood.metrics;
|
|
108
|
+
return [
|
|
109
|
+
`${task.taskOrdinal}. ${task.caseId}`,
|
|
110
|
+
withReason(metrics.changedFileCount),
|
|
111
|
+
withReason(metrics.changedSymbolCount),
|
|
112
|
+
withReason(metrics.affectedNodeCount),
|
|
113
|
+
withReason(metrics.affectedEdgeCount),
|
|
114
|
+
withReason(metrics.taskOverlapCount),
|
|
115
|
+
withReason(metrics.taskOverlapPercent),
|
|
116
|
+
];
|
|
117
|
+
});
|
|
118
|
+
const explanations = assessed.map((task) => `Task ${task.taskOrdinal}: ${task.affectedNeighborhood.recommendationExplanation}`);
|
|
119
|
+
const detailRows = assessed.flatMap((task) => {
|
|
120
|
+
const evidence = task.affectedNeighborhood;
|
|
121
|
+
const label = `${task.taskOrdinal}. ${task.caseId}`;
|
|
122
|
+
return [
|
|
123
|
+
...evidence.unresolvedTaskMappings.items.map((entry) => [label, `unresolved ${entry.subject}`, entry.name, entry.reason]),
|
|
124
|
+
...evidence.ambiguousTaskSymbols.items.map((entry) => [label, "ambiguous expected-symbol", entry.name, entry.candidateNodeIds.join(", ")]),
|
|
125
|
+
...evidence.warnings.items.map((warning) => [label, "warning", warning.code, warning.message]),
|
|
126
|
+
];
|
|
127
|
+
});
|
|
128
|
+
const omitted = assessed.flatMap((task) => {
|
|
129
|
+
const evidence = task.affectedNeighborhood;
|
|
130
|
+
return [
|
|
131
|
+
...boundedNote("affected node IDs", task.taskOrdinal, evidence.affectedNodeIds),
|
|
132
|
+
...boundedNote("participating edge IDs", task.taskOrdinal, evidence.participatingEdgeIds),
|
|
133
|
+
...boundedNote("unresolved task mappings", task.taskOrdinal, evidence.unresolvedTaskMappings),
|
|
134
|
+
...boundedNote("ambiguous task symbols", task.taskOrdinal, evidence.ambiguousTaskSymbols),
|
|
135
|
+
...boundedNote("warnings", task.taskOrdinal, evidence.warnings),
|
|
136
|
+
];
|
|
137
|
+
});
|
|
138
|
+
return `<h4>Affected Neighborhood</h4>
|
|
139
|
+
${overview}
|
|
140
|
+
${metricRows.length === 0 ? "" : table(["Task", "Changed files", "Changed symbols", "Affected nodes", "Affected edges", "Task-overlap nodes", "Task-overlap percent"], metricRows)}
|
|
141
|
+
${explanations.length === 0 ? "" : list(explanations)}
|
|
142
|
+
${detailRows.length === 0 ? "" : table(["Task", "Evidence type", "Name", "Detail"], detailRows)}
|
|
143
|
+
${omitted.length === 0 ? "" : list(omitted)}`;
|
|
144
|
+
}
|
|
48
145
|
function renderFreshnessSummary(summary) {
|
|
49
146
|
return `<h3>Index Freshness</h3>
|
|
50
147
|
${table(["Field", "Value"], [
|
|
@@ -184,6 +281,7 @@ function renderProject(project, index, agentId) {
|
|
|
184
281
|
formatMetric(task.warm.cumulativeEstimatedContextTokens),
|
|
185
282
|
]))}
|
|
186
283
|
${renderProjectFreshness(project.tasks)}
|
|
284
|
+
${renderProjectAffectedNeighborhood(project.tasks)}
|
|
187
285
|
<p class="muted">Estimated tokens are character-based context-size estimates, not provider token usage.</p>
|
|
188
286
|
<p class="muted">${agentColumnsNote(agentId)}</p>
|
|
189
287
|
${table([
|
|
@@ -2,3 +2,8 @@ export const WARM_INDEX_REUSE_REPORT_SCHEMA_VERSION = "my-dev-kit-lab-warm-index
|
|
|
2
2
|
/** Report presentation limits; the full persisted lists stay in warm-index-execution.json. */
|
|
3
3
|
export const MAX_REPORT_FRESHNESS_CHANGES = 20;
|
|
4
4
|
export const MAX_REPORT_FRESHNESS_UNRESOLVED = 10;
|
|
5
|
+
export const MAX_REPORT_AFFECTED_NODES = 20;
|
|
6
|
+
export const MAX_REPORT_AFFECTED_EDGES = 20;
|
|
7
|
+
export const MAX_REPORT_AFFECTED_TASK_UNRESOLVED = 10;
|
|
8
|
+
export const MAX_REPORT_AFFECTED_TASK_AMBIGUOUS = 10;
|
|
9
|
+
export const MAX_REPORT_AFFECTED_WARNINGS = 10;
|
package/docs/METRICS.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Metrics
|
|
2
2
|
|
|
3
|
-
This document is the canonical metric glossary for my-dev-kit-lab. It defines every metric that appears in benchmark profiles, prompt variants, controlled experiment artifacts, and rendered reports.
|
|
3
|
+
This document is the canonical metric glossary for my-dev-kit-lab. It defines every metric that appears in benchmark profiles, prompt variants, controlled experiment artifacts, and rendered reports across the registered experiment plugins (`context-strategy-comparison`, `warm-index-reuse`, and `incremental-change-staleness`).
|
|
4
4
|
|
|
5
5
|
Related documentation:
|
|
6
6
|
- [ARCHITECTURE.md](ARCHITECTURE.md) — how metrics flow through the pipeline
|
|
@@ -463,7 +463,7 @@ The released v0.5.1 expanded benchmark suite adds no warm-index metric and chang
|
|
|
463
463
|
- `unavailable`: `value` is `null` and `reason` explains the missing or invalid evidence. Missing evidence is never reported as zero, and a non-finite upstream number becomes `unavailable` with an invalid-input reason.
|
|
464
464
|
- `not-applicable`: `value` is `null` and `reason` explains why the metric does not apply. The contract supports it; the current warm-index metrics do not emit it.
|
|
465
465
|
|
|
466
|
-
Units are `ms`, `characters`, `estimated-tokens`, `tokens`, and `score
|
|
466
|
+
Units are `ms`, `characters`, `estimated-tokens`, `tokens`, and `score`; the affected-neighborhood metrics below add `count` and `percent` in v0.6.1. Sources are `measured`, `derived`, `estimated-chars-div-4`, and `agent`. `tokenCountMethod` is set only for estimated-token metrics (currently `estimated_chars_div_4`).
|
|
467
467
|
|
|
468
468
|
**Task ordinal.** Tasks keep their filtered source order inside each benchmark project and are numbered `1, 2, 3, …` per project. Ordinals, amortization, and every cumulative sum restart for each benchmark project.
|
|
469
469
|
|
|
@@ -513,7 +513,7 @@ The plugin evaluates each task side that has context evidence once with the dete
|
|
|
513
513
|
- Cumulative fake-agent total tokens (`tokens`, `agent`)
|
|
514
514
|
Formula: sum of fake-agent total tokens for tasks `1..N` on that side, under the strict-prefix rule.
|
|
515
515
|
|
|
516
|
-
**Generic outcome metrics.** Available values also appear as `ExperimentMetric` entries on each outcome, with `variantId`, `caseId`, `unit`, and `description`. Both the `raw-full-file` and `warm-index-reuse` outcomes can carry `context-character-count`, `context-estimated-token-count`, `operation-duration-ms` (raw context construction or warm retrieval), `cumulative-component-duration-ms`, `cumulative-context-estimated-token-count`, `agent-correctness-score`, `agent-total-tokens`, and `cumulative-agent-total-tokens`. The warm outcome also carries `amortized-index-build-duration-ms
|
|
516
|
+
**Generic outcome metrics.** Available values also appear as `ExperimentMetric` entries on each outcome, with `variantId`, `caseId`, `unit`, and `description`. Both the `raw-full-file` and `warm-index-reuse` outcomes can carry `context-character-count`, `context-estimated-token-count`, `operation-duration-ms` (raw context construction or warm retrieval), `cumulative-component-duration-ms`, `cumulative-context-estimated-token-count`, `agent-correctness-score`, `agent-total-tokens`, and `cumulative-agent-total-tokens`. The warm outcome also carries `amortized-index-build-duration-ms` and the six released v0.6.1 affected-neighborhood metrics listed below. Unavailable metrics are omitted rather than emitted as zero. Run-level metrics are limited to `warm-index-project-count`, `warm-index-task-count`, and `warm-index-session-prepared-project-count`.
|
|
517
517
|
|
|
518
518
|
**Interpretation.** The report separates the one-time index cost from per-task retrieval cost and shows the fixed cost falling per task as more tasks reuse the index. It calculates no token-savings percentage, duration-reduction percentage, break-even task, composite score, winner, or ranking. Warm-index plots map the same precomputed values: amortized index build duration, raw versus retrieved estimated context tokens, fake-agent correctness (or campaign agent correctness for a `--campaign` run), and cumulative fake-agent total tokens. Unavailable values become skipped plot points with their reason.
|
|
519
519
|
|
|
@@ -553,3 +553,79 @@ Before each task's warm retrieval, the warm-index run compares the files the ind
|
|
|
553
553
|
- **`unknown` versus not assessed.** `unknown` means an assessment ran and could not prove freshness. A task with no assessment (for example a report from before v0.6.0) is shown as `not assessed` and counted as unassessed, never as `unknown`.
|
|
554
554
|
- **Report-level counts.** `report.warmIndexReuse.indexFreshnessSummary` holds direct presentation counts of assessed, unassessed, `fresh`, `stale`, `partially-stale`, and `unknown` tasks (assessed equals the sum of the four statuses). They are counts, not `ExperimentMetric` formulas: no percentage, score, or ranking is calculated. Each task's `indexFreshness` lists at most 20 changed files and 10 unresolved comparisons, with the persisted totals and the omitted count; hashes stay in `warm-index-execution.json`.
|
|
555
555
|
- **Observational only.** Freshness does not trigger reindexing, suppress or alter warm retrieval, or change execution status, correctness, provider or agent outcome status, or token evidence. v0.6.0 makes no reindex recommendation.
|
|
556
|
+
|
|
557
|
+
### Affected-neighborhood metrics (v0.6.1)
|
|
558
|
+
|
|
559
|
+
Status: released in v0.6.1. This evidence extends the warm-index evidence above without a new command, flag, plugin, plot, or gallery item.
|
|
560
|
+
|
|
561
|
+
For each task, after that task's freshness assessment and before its warm retrieval, the run maps the confirmed changed indexed files, and the baseline symbols they contain, onto the baseline my-dev-kit graph of the prepared warm index, takes the exactly one-hop neighborhood, maps the task's `expectedFiles` and `expectedSymbols` onto the same graph, and compares them. The assessment is persisted as the per-task `affectedNeighborhood` in `warm-index-execution.json`. The metric owner (`metrics.ts`) converts that persisted assessment into the six warm-side metrics below exactly once; the report renders the metric owner's objects and never recomputes them.
|
|
562
|
+
|
|
563
|
+
All six metrics are warm-side only (they are not on the raw-full-file side, because raw context construction does not own baseline index graph evidence), have source `derived`, and carry the standard availability contract: `available` with a finite value (zero is a valid available value) or `unavailable` with a reason. An absent or `null` assessment makes all six unavailable with the reason "No affected-neighborhood assessment was recorded for this task."; a `null` field in a performed assessment is unavailable with a reason naming the assessment, seed-mapping, graph, neighborhood, and task-mapping statuses. Missing evidence is never reported as zero.
|
|
564
|
+
|
|
565
|
+
- `changedFileCount` (`count`)
|
|
566
|
+
Meaning: the number of unique confirmed modified or missing files that the index snapshot represents.
|
|
567
|
+
Availability: available (including 0 for a fresh index) when freshness gave a basis for a changed-file list; unavailable when freshness is `unknown`.
|
|
568
|
+
Limitations: files the snapshot does not represent, including new files, are not counted.
|
|
569
|
+
- `changedSymbolCount` (`count`)
|
|
570
|
+
Meaning: the number of unique baseline symbol identities (file path plus symbol name) that belong to confirmed changed indexed files, read from the baseline symbol index.
|
|
571
|
+
Limitations: it does not prove that any individual counted symbol's own source text changed; it means the symbol was present in a changed indexed file. A symbol counts even when its graph node cannot be resolved, so mapping completeness is separate from this count. Duplicate same-file, same-name records count once. It is unavailable when the baseline symbol index is unavailable.
|
|
572
|
+
- `affectedNodeCount` (`count`)
|
|
573
|
+
Meaning: the number of unique graph node IDs among the resolved seed nodes plus their direct one-hop neighbors.
|
|
574
|
+
Formula: exactly one hop, bidirectional (either endpoint of an edge may be the seed), over every structurally valid retained edge kind, with no weighting by kind. Unresolved seed identities are not counted as affected nodes.
|
|
575
|
+
Availability: 0 for a complete empty seed set (for example a fresh index); unavailable when the seed mapping or graph evidence is unavailable.
|
|
576
|
+
- `affectedEdgeCount` (`count`)
|
|
577
|
+
Meaning: the number of unique baseline graph edges incident to at least one resolved seed node (the edges actually followed by the one-hop expansion).
|
|
578
|
+
Limitations: it is not the number of edges among all affected nodes; an edge between two non-seed neighbors is not counted.
|
|
579
|
+
- `taskOverlapCount` (`count`)
|
|
580
|
+
Meaning: the number of unique graph nodes that are both in the affected neighborhood and among the task's resolved task nodes.
|
|
581
|
+
Availability: evaluable only when both the neighborhood and the task mapping are usable.
|
|
582
|
+
- `taskOverlapPercent` (`percent`)
|
|
583
|
+
Formula: `taskOverlapCount / resolvableTaskNodeCount * 100`, unrounded.
|
|
584
|
+
Denominator: `resolvableTaskNodeCount` is the number of unique graph nodes resolved from the task's `expectedFiles` (exact `file:<path>` nodes) and its unambiguous `expectedSymbols`. An expected symbol resolves only within the task's own expected files, only when exactly one candidate exists (zero is unresolved, several is ambiguous), and never by a global lookup. Unresolved and ambiguous metadata are excluded from the denominator.
|
|
585
|
+
Availability: unavailable, never zero, when there are no resolvable task nodes or overlap cannot be evaluated. When task mapping is partial, the percentage covers only the resolvable subset.
|
|
586
|
+
|
|
587
|
+
**Generic outcome metrics.** Available values appear as `ExperimentMetric` entries on the warm outcome only: `affected-neighborhood-changed-file-count`, `affected-neighborhood-changed-symbol-count`, `affected-neighborhood-node-count`, `affected-neighborhood-edge-count`, `affected-neighborhood-task-overlap-count`, and `affected-neighborhood-task-overlap-percent`. Unavailable metrics stay explicit in the structured metrics and the report but are omitted from these entries rather than emitted as zero. There is no run-level aggregate, average, ranking, or score for this evidence.
|
|
588
|
+
|
|
589
|
+
**Categorical evidence (not metrics).** The assessment also records, and the report presents, two categories that are deliberately not numeric `ExperimentMetric` entries:
|
|
590
|
+
|
|
591
|
+
- `relationship`: `related` when the confirmed overlap is greater than zero (even when other evidence is partial); `unrelated` only when the seed mapping, neighborhood, and task mapping are all complete and the overlap is zero; `unknown` for zero observed overlap under incomplete or unavailable evidence (for example partially-stale freshness, an unresolved changed file or symbol, a partial graph, an unresolved or ambiguous expected file or symbol, or no resolvable task nodes).
|
|
592
|
+
- `reindexRecommendation`: `recommended` for `related`, `not-indicated` for `unrelated`, and `unknown` for `unknown`. There is no freshness-only shortcut: a fresh index with an empty affected neighborhood and incomplete task mapping is `unknown` / `unknown`. `not-indicated` means only that this bounded analysis found no evidence supporting reindexing for that task; it does not mean skipping a reindex is universally safe, that the whole repository is fresh, that no new non-indexed file exists, or that reindexing could never help.
|
|
593
|
+
- The assessment `status` (`complete`, `partial`, or `unavailable`) says how complete the evidence was. A `partial` assessment can still be `related` and `recommended`. A `null` per-task value means no assessment was performed, an absent value means a pre-v0.6.1 artifact, and an assessment whose status is `unavailable` means it was performed but the evidence could not establish a usable result; these are reported separately.
|
|
594
|
+
|
|
595
|
+
**Report presentation.** `report.warmIndexReuse.affectedNeighborhoodSummary` holds presentation counts (assessed and unassessed tasks; complete, partial, and unavailable assessments; related, unrelated, and unknown relationships; recommended, not-indicated, and unknown recommendations). They are counts, not metric formulas. Each task's `affectedNeighborhood` block shows the statuses, the six metric objects, the relationship, the recommendation with a fixed explanation, and bounded lists (at most 20 affected node IDs, 20 edge IDs, 10 unresolved and 10 ambiguous task mappings, and 10 warnings, each with its total and omitted count). The full identity arrays stay in `warm-index-execution.json`.
|
|
596
|
+
|
|
597
|
+
**Observational only.** The evidence never triggers reindexing, suppresses or alters warm retrieval, or changes execution, provider, agent, correctness, or token-evidence status. It uses only the baseline graph of the prepared index: there is no graph-diff, refreshed-index, or stale-versus-refreshed comparison.
|
|
598
|
+
|
|
599
|
+
## Incremental-change and staleness evidence (v0.6.2)
|
|
600
|
+
|
|
601
|
+
The `incremental-change-staleness` plugin reuses the six existing v0.6.1 affected-neighborhood numeric metrics unchanged (`changedFileCount`, `changedSymbolCount`, `affectedNodeCount`, `affectedEdgeCount`, `taskOverlapCount`, `taskOverlapPercent`; see "Affected-neighborhood metrics (v0.6.1)" above) and adds no new numeric formula.
|
|
602
|
+
|
|
603
|
+
**Correctness relation** (per scenario, comparing the `stale-index` and `full-refresh` treatments' deterministic fake-agent correctness):
|
|
604
|
+
|
|
605
|
+
- `stale-worse`: `stale-index` correctness is lower than `full-refresh` correctness.
|
|
606
|
+
- `same`: both treatments have available, equal correctness.
|
|
607
|
+
- `stale-better`: `stale-index` correctness is higher than `full-refresh` correctness.
|
|
608
|
+
- `unknown`: either treatment's correctness is unavailable.
|
|
609
|
+
|
|
610
|
+
**Required-file status** (per treatment, an answer-key file-presence check against that treatment's actual retrieved-files evidence — not retrieval precision, recall, F1, MRR, NDCG, or symbol recall):
|
|
611
|
+
|
|
612
|
+
- `present`: every required file was observed.
|
|
613
|
+
- `missing`: at least one required file was not observed, and the observed-files evidence was complete.
|
|
614
|
+
- `unknown`: the actual-files-read evidence was not complete enough to establish presence or absence.
|
|
615
|
+
|
|
616
|
+
**Required-file relation** (per scenario, comparing the two treatments' required-file status):
|
|
617
|
+
|
|
618
|
+
- `stale-worse`: `stale-index` is `missing` while `full-refresh` is `present`.
|
|
619
|
+
- `same`: both treatments have the same status (including `missing`/`missing`).
|
|
620
|
+
- `stale-better`: `stale-index` is `present` while `full-refresh` is `missing`.
|
|
621
|
+
- `unknown`: either treatment's status is `unknown`.
|
|
622
|
+
|
|
623
|
+
**Stale-risk classification** (per scenario, the frozen precedence over the two relations above):
|
|
624
|
+
|
|
625
|
+
1. `observed-stale-regression` — the correctness relation or the required-file relation is `stale-worse`.
|
|
626
|
+
2. `inconclusive` — otherwise, if the correctness relation or the required-file relation is `unknown`.
|
|
627
|
+
3. `no-observed-stale-regression` — otherwise.
|
|
628
|
+
|
|
629
|
+
`staleRiskClassification` is not a numeric risk score, not a winner, not a ranking, and not an automatic reindex decision. `no-observed-stale-regression` means only that this matched scenario did not observe a stale-specific regression under these two frozen evidence dimensions; it does not establish that stale indexes are generally safe. `reindexRecommendation` (the released v0.6.1 categorical evidence) remains separate observational evidence about the affected-neighborhood analysis; it does not select or trigger either treatment and is not an input to the stale-risk classification above.
|
|
630
|
+
|
|
631
|
+
**Report presentation.** `report.incrementalChangeStaleness` holds `scenarioCount`, `readyScenarioCount`, `failedScenarioCount`, `observedStaleRegressionCount`, `noObservedStaleRegressionCount`, `inconclusiveCount`, per-scenario evidence (lifecycle, both treatments in order, and the comparison), and fixed limitations text. There is no overall score, grade, winner, best-treatment, or safe-to-skip-reindex field anywhere in the report or the persisted execution artifact.
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
|
-
|
|
5
|
-
const sourceRoot = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
-
const targetRoot = process.argv[2];
|
|
7
|
-
if (!targetRoot) {
|
|
8
|
-
console.error("prepare requires a target root argument");
|
|
9
|
-
process.exit(2);
|
|
10
|
-
}
|
|
11
|
-
fs.mkdirSync(path.join(targetRoot, "app"), { recursive: true });
|
|
12
|
-
fs.mkdirSync(path.join(targetRoot, "out"), { recursive: true });
|
|
13
|
-
for (const file of ["index.html", "server.mjs"]) fs.copyFileSync(path.join(sourceRoot, file), path.join(targetRoot, "app", file));
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
const sourceRoot = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
const targetRoot = process.argv[2];
|
|
7
|
+
if (!targetRoot) {
|
|
8
|
+
console.error("prepare requires a target root argument");
|
|
9
|
+
process.exit(2);
|
|
10
|
+
}
|
|
11
|
+
fs.mkdirSync(path.join(targetRoot, "app"), { recursive: true });
|
|
12
|
+
fs.mkdirSync(path.join(targetRoot, "out"), { recursive: true });
|
|
13
|
+
for (const file of ["index.html", "server.mjs"]) fs.copyFileSync(path.join(sourceRoot, file), path.join(targetRoot, "app", file));
|
|
14
14
|
fs.writeFileSync(path.join(targetRoot, "out", "report.json"), JSON.stringify({ prepared: true, counts: { widgets: 3 } }, null, 2), "utf8");
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import http from "node:http";
|
|
2
|
-
import fs from "node:fs";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
5
|
-
|
|
6
|
-
const root = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
-
const port = Number(process.env.TUTORIAL_FIXTURE_PORT || 0);
|
|
8
|
-
const server = http.createServer((req, res) => {
|
|
9
|
-
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
10
|
-
if (url.pathname === "/api/status") { res.writeHead(200, { "content-type": "application/json" }); res.end(JSON.stringify({ ok: true, widgets: 3 })); return; }
|
|
11
|
-
const filePath = url.pathname === "/" ? path.join(root, "index.html") : path.join(root, url.pathname);
|
|
12
|
-
fs.readFile(filePath, (error, contents) => { if (error) { res.writeHead(404); res.end("not found"); return; } res.writeHead(200, { "content-type": filePath.endsWith(".html") ? "text/html" : "application/octet-stream" }); res.end(contents); });
|
|
13
|
-
});
|
|
1
|
+
import http from "node:http";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
const root = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const port = Number(process.env.TUTORIAL_FIXTURE_PORT || 0);
|
|
8
|
+
const server = http.createServer((req, res) => {
|
|
9
|
+
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
10
|
+
if (url.pathname === "/api/status") { res.writeHead(200, { "content-type": "application/json" }); res.end(JSON.stringify({ ok: true, widgets: 3 })); return; }
|
|
11
|
+
const filePath = url.pathname === "/" ? path.join(root, "index.html") : path.join(root, url.pathname);
|
|
12
|
+
fs.readFile(filePath, (error, contents) => { if (error) { res.writeHead(404); res.end("not found"); return; } res.writeHead(200, { "content-type": filePath.endsWith(".html") ? "text/html" : "application/octet-stream" }); res.end(contents); });
|
|
13
|
+
});
|
|
14
14
|
server.listen(port, "127.0.0.1", () => console.log("tutorial fixture listening on " + server.address().port));
|