@dailephd/my-dev-kit-lab 0.6.0 → 0.6.1
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 +18 -0
- package/README.md +5 -3
- package/dist/src/evaluation/affectedNeighborhood.js +712 -0
- package/dist/src/evaluation/indexSnapshot.js +1 -1
- 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 +71 -0
- package/dist/src/experiments/plugins/warmIndexReuse/warmIndexSession.js +9 -1
- package/dist/src/report/experiments/buildPluginExperimentReport.js +14 -2
- package/dist/src/report/experiments/buildWarmIndexReuseReport.js +80 -3
- package/dist/src/report/experiments/renderPluginExperimentReportText.js +84 -0
- package/dist/src/report/experiments/renderWarmIndexReuseHtml.js +98 -0
- package/dist/src/report/experiments/warmIndexReuseReportModel.js +5 -0
- package/docs/METRICS.md +44 -2
- package/examples/tutorial-browser/prepare.mjs +13 -13
- package/examples/tutorial-browser/server.mjs +13 -13
- package/package.json +1 -1
|
@@ -228,7 +228,7 @@ async function enumerateIndexArtifacts(indexDir) {
|
|
|
228
228
|
artifacts.sort((left, right) => compareCodeUnits(left.path, right.path));
|
|
229
229
|
return { artifacts, truncated };
|
|
230
230
|
}
|
|
231
|
-
async function readJson(filePath) {
|
|
231
|
+
export async function readJson(filePath) {
|
|
232
232
|
let text;
|
|
233
233
|
try {
|
|
234
234
|
text = await readFile(filePath, "utf8");
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { resolveWithinRoot } from "../../../core/pathSafety.js";
|
|
3
|
+
import { assessAffectedNeighborhood, mapAffectedNeighborhoodSeeds, } from "../../../evaluation/affectedNeighborhood.js";
|
|
3
4
|
import { assessIndexFreshness } from "../../../evaluation/indexFreshness.js";
|
|
4
5
|
import { runMyDevKitRetrievalFromIndex } from "../../../evaluation/runMyDevKitRetrieval.js";
|
|
5
6
|
import { runRawFullFileBaseline } from "../../../evaluation/runRawFullFileBaseline.js";
|
|
@@ -92,6 +93,7 @@ async function executeTask(args) {
|
|
|
92
93
|
let warmRetrieval;
|
|
93
94
|
let warmStatus = "failed";
|
|
94
95
|
let indexFreshness;
|
|
96
|
+
let affectedNeighborhood = null;
|
|
95
97
|
if (!args.session) {
|
|
96
98
|
errors.push(args.setupFailure ?? { side: "warm", code: "warm-index-setup-failed", message: "No warm index session." });
|
|
97
99
|
// No snapshot exists, so freshness is conservatively unknown rather than assumed.
|
|
@@ -115,6 +117,24 @@ async function executeTask(args) {
|
|
|
115
117
|
targetRoot: args.session.targetRoot,
|
|
116
118
|
sourceRoots: args.session.sourceRoots,
|
|
117
119
|
});
|
|
120
|
+
// Observational and in-memory: this task's freshness against the shared baseline graph. It
|
|
121
|
+
// reads no files, runs no my-dev-kit command, and cannot stop the retrieval below.
|
|
122
|
+
try {
|
|
123
|
+
const seedMapping = mapAffectedNeighborhoodSeeds({
|
|
124
|
+
indexSnapshot: args.session.indexSnapshot,
|
|
125
|
+
freshness: indexFreshness,
|
|
126
|
+
graph: args.session.affectedNeighborhoodGraph,
|
|
127
|
+
});
|
|
128
|
+
affectedNeighborhood = assessAffectedNeighborhood({
|
|
129
|
+
graph: args.session.affectedNeighborhoodGraph,
|
|
130
|
+
seedMapping,
|
|
131
|
+
task: evaluationCase,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
affectedNeighborhood = null;
|
|
136
|
+
warnings.push(`Affected-neighborhood assessment was not recorded: ${errorMessage(error)}`);
|
|
137
|
+
}
|
|
118
138
|
try {
|
|
119
139
|
// requireKit: false retains subordinate command evidence; a skipped result caused by a
|
|
120
140
|
// failed command is classified as failed below rather than as an honest skip.
|
|
@@ -149,6 +169,7 @@ async function executeTask(args) {
|
|
|
149
169
|
rawBaseline,
|
|
150
170
|
warmRetrieval,
|
|
151
171
|
indexFreshness,
|
|
172
|
+
affectedNeighborhood,
|
|
152
173
|
warnings,
|
|
153
174
|
errors,
|
|
154
175
|
};
|
|
@@ -47,6 +47,7 @@ function summarizeTask(task) {
|
|
|
47
47
|
}
|
|
48
48
|
: null,
|
|
49
49
|
indexFreshness: task.indexFreshness ? structuredClone(task.indexFreshness) : null,
|
|
50
|
+
affectedNeighborhood: task.affectedNeighborhood ? structuredClone(task.affectedNeighborhood) : null,
|
|
50
51
|
warnings: [...task.warnings],
|
|
51
52
|
errors: task.errors.map((error) => ({ ...error })),
|
|
52
53
|
};
|
|
@@ -147,6 +147,7 @@ export function calculateWarmIndexProjectMetrics(project, agentEvidence) {
|
|
|
147
147
|
cumulativeComponentDurationMs: warmDuration.add(warm.retrievalDurationMs, label("warm retrieval duration")),
|
|
148
148
|
cumulativeEstimatedContextTokens: warmTokens.add(warm.contextEstimatedTokens, label("warm estimated context tokens")),
|
|
149
149
|
...warmAgent,
|
|
150
|
+
...affectedNeighborhoodMetrics(task),
|
|
150
151
|
cumulativeAgentTotalTokens: warmAgentTokens.add(warmAgent.agentTotalTokens, label("warm agent total tokens")),
|
|
151
152
|
},
|
|
152
153
|
};
|
|
@@ -192,6 +193,35 @@ function warmDirect(task) {
|
|
|
192
193
|
retrievalDurationMs: availableMetric(warm.durationMs, "ms", "measured"),
|
|
193
194
|
};
|
|
194
195
|
}
|
|
196
|
+
const NO_AFFECTED_NEIGHBORHOOD_REASON = "No affected-neighborhood assessment was recorded for this task.";
|
|
197
|
+
/**
|
|
198
|
+
* Converts the persisted affected-neighborhood assessment into the six warm-side metrics, once.
|
|
199
|
+
* A finite value (including zero) is available; an absent assessment or a null field is
|
|
200
|
+
* unavailable with a reason, never zero. The metric layer alone owns these value objects.
|
|
201
|
+
*/
|
|
202
|
+
function affectedNeighborhoodMetrics(task) {
|
|
203
|
+
const assessment = task.affectedNeighborhood;
|
|
204
|
+
const one = (field, label) => {
|
|
205
|
+
const unit = field === "taskOverlapPercent" ? "percent" : "count";
|
|
206
|
+
if (!assessment)
|
|
207
|
+
return unavailableMetric(unit, "derived", NO_AFFECTED_NEIGHBORHOOD_REASON);
|
|
208
|
+
const value = assessment[field];
|
|
209
|
+
if (typeof value === "number")
|
|
210
|
+
return availableMetric(value, unit, "derived");
|
|
211
|
+
const context = `assessment ${assessment.status}; seed mapping ${assessment.seedMappingStatus}; ` +
|
|
212
|
+
`graph ${assessment.graphEvidenceStatus}; neighborhood ${assessment.neighborhoodStatus}; task mapping ${assessment.taskMapping.status}` +
|
|
213
|
+
(field === "taskOverlapPercent" ? `; resolvable task nodes ${assessment.taskMapping.resolvableTaskNodeCount}` : "");
|
|
214
|
+
return unavailableMetric(unit, "derived", `Affected-neighborhood evidence did not establish ${label} (${context}).`);
|
|
215
|
+
};
|
|
216
|
+
return {
|
|
217
|
+
changedFileCount: one("changedFileCount", "the changed indexed file count"),
|
|
218
|
+
changedSymbolCount: one("changedSymbolCount", "the changed baseline symbol count"),
|
|
219
|
+
affectedNodeCount: one("affectedNodeCount", "the affected graph node count"),
|
|
220
|
+
affectedEdgeCount: one("affectedEdgeCount", "the affected graph edge count"),
|
|
221
|
+
taskOverlapCount: one("taskOverlapCount", "the task-overlap node count"),
|
|
222
|
+
taskOverlapPercent: one("taskOverlapPercent", "the task-overlap percent"),
|
|
223
|
+
};
|
|
224
|
+
}
|
|
195
225
|
/** Pure, order-preserving calculation; ordinals and cumulative state restart for every project. */
|
|
196
226
|
export function calculateWarmIndexMetrics(projects, agentEvidence) {
|
|
197
227
|
if (agentEvidence && agentEvidence.length !== projects.length) {
|
|
@@ -222,8 +252,49 @@ export function toWarmOutcomeMetrics(task, variantId) {
|
|
|
222
252
|
{ id: "cumulative-context-estimated-token-count", name: "Cumulative estimated context tokens", description: "Sum of retrieved estimated context tokens for tasks 1..N in this project; not provider token usage.", metric: task.warm.cumulativeEstimatedContextTokens },
|
|
223
253
|
{ id: "amortized-index-build-duration-ms", name: "Amortized index build duration", description: "Index build duration divided by this task's ordinal within its project.", metric: task.warm.amortizedIndexBuildDurationMs },
|
|
224
254
|
...agentSpecs(task.warm),
|
|
255
|
+
...affectedNeighborhoodSpecs(task.warm),
|
|
225
256
|
]);
|
|
226
257
|
}
|
|
258
|
+
function affectedNeighborhoodSpecs(warm) {
|
|
259
|
+
return [
|
|
260
|
+
{
|
|
261
|
+
id: "affected-neighborhood-changed-file-count",
|
|
262
|
+
name: "Changed indexed files",
|
|
263
|
+
description: "Unique confirmed modified or missing files that the baseline index snapshot represents; new or non-indexed files are not counted.",
|
|
264
|
+
metric: warm.changedFileCount,
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
id: "affected-neighborhood-changed-symbol-count",
|
|
268
|
+
name: "Changed baseline symbols",
|
|
269
|
+
description: "Baseline indexed symbol identities (file path + name) belonging to confirmed changed indexed files; not proof that each symbol's own source text changed.",
|
|
270
|
+
metric: warm.changedSymbolCount,
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
id: "affected-neighborhood-node-count",
|
|
274
|
+
name: "Affected graph nodes",
|
|
275
|
+
description: "Unique resolved seed nodes plus their direct one-hop baseline graph neighbors, over edges of any kind and direction.",
|
|
276
|
+
metric: warm.affectedNodeCount,
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
id: "affected-neighborhood-edge-count",
|
|
280
|
+
name: "Affected graph edges",
|
|
281
|
+
description: "Unique baseline graph edges incident to at least one resolved seed node; not the induced subgraph of all affected nodes.",
|
|
282
|
+
metric: warm.affectedEdgeCount,
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
id: "affected-neighborhood-task-overlap-count",
|
|
286
|
+
name: "Task-overlap nodes",
|
|
287
|
+
description: "Unique baseline graph nodes that are both in the affected neighborhood and resolved from the task's expected files and symbols.",
|
|
288
|
+
metric: warm.taskOverlapCount,
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
id: "affected-neighborhood-task-overlap-percent",
|
|
292
|
+
name: "Task-overlap percent",
|
|
293
|
+
description: "Task-overlap nodes as a percentage of resolvable task graph nodes; unresolved or ambiguous expected metadata is not in the denominator.",
|
|
294
|
+
metric: warm.taskOverlapPercent,
|
|
295
|
+
},
|
|
296
|
+
];
|
|
297
|
+
}
|
|
227
298
|
function agentSpecs(side) {
|
|
228
299
|
return [
|
|
229
300
|
{
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { buildMyDevKitIndex, probeMyDevKitVersion } from "../../../evaluation/runMyDevKitRetrieval.js";
|
|
4
|
+
import { loadAffectedNeighborhoodGraphEvidence } from "../../../evaluation/affectedNeighborhood.js";
|
|
4
5
|
import { captureIndexSnapshot } from "../../../evaluation/indexSnapshot.js";
|
|
5
6
|
/**
|
|
6
7
|
* Builds the index once through the shared `buildMyDevKitIndex` owner. A build that reports
|
|
@@ -31,13 +32,20 @@ export async function prepareWarmIndexSession(options) {
|
|
|
31
32
|
command: build.command,
|
|
32
33
|
tool
|
|
33
34
|
});
|
|
35
|
+
// Loaded once per session from the manifest-referenced artifacts; never fails the session.
|
|
36
|
+
const affectedNeighborhoodGraph = await loadAffectedNeighborhoodGraphEvidence({
|
|
37
|
+
indexDir: build.indexDir,
|
|
38
|
+
sourceRoots,
|
|
39
|
+
indexSnapshot
|
|
40
|
+
});
|
|
34
41
|
const session = Object.freeze({
|
|
35
42
|
indexDir: build.indexDir,
|
|
36
43
|
buildDurationMs: build.durationMs,
|
|
37
44
|
targetRoot: options.target.absoluteTargetRoot,
|
|
38
45
|
sourceRoots,
|
|
39
46
|
buildCommand: build.command,
|
|
40
|
-
indexSnapshot
|
|
47
|
+
indexSnapshot,
|
|
48
|
+
affectedNeighborhoodGraph
|
|
41
49
|
});
|
|
42
50
|
return { ok: true, session, build };
|
|
43
51
|
}
|
|
@@ -107,6 +107,18 @@ function freshnessSentence(report) {
|
|
|
107
107
|
`fresh=${summary.freshTaskCount}, stale=${summary.staleTaskCount}, partially-stale=${summary.partiallyStaleTaskCount}, unknown=${summary.unknownTaskCount}. ` +
|
|
108
108
|
"Freshness is observational evidence over files represented by the index snapshot and does not change retrieval or provider status.");
|
|
109
109
|
}
|
|
110
|
+
/** Neutral, count-only sentence derived from the report's persisted affected-neighborhood summary. */
|
|
111
|
+
function affectedNeighborhoodSentence(report) {
|
|
112
|
+
const summary = report.affectedNeighborhoodSummary;
|
|
113
|
+
if (!summary || summary.assessedTaskCount === 0) {
|
|
114
|
+
return "No per-task affected-neighborhood assessment was available in this report.";
|
|
115
|
+
}
|
|
116
|
+
return (`Affected-neighborhood evidence was available for ${summary.assessedTaskCount} task boundar${summary.assessedTaskCount === 1 ? "y" : "ies"}: ` +
|
|
117
|
+
`complete=${summary.completeAssessmentCount}, partial=${summary.partialAssessmentCount}, unavailable=${summary.unavailableAssessmentCount}; ` +
|
|
118
|
+
`relationships were related=${summary.relatedTaskCount}, unrelated=${summary.unrelatedTaskCount}, unknown=${summary.unknownRelationshipTaskCount}; ` +
|
|
119
|
+
`reindex recommendations were recommended=${summary.recommendedReindexCount}, not-indicated=${summary.notIndicatedReindexCount}, unknown=${summary.unknownReindexRecommendationCount}. ` +
|
|
120
|
+
"Affected-neighborhood evidence uses a one-hop baseline graph around confirmed changed indexed files/symbols. It is observational and does not alter retrieval or execution status.");
|
|
121
|
+
}
|
|
110
122
|
function buildInterpretation(run, contextStrategyComparisonV043, warmIndexReuse) {
|
|
111
123
|
if (run.pluginId === "warm-index-reuse" && warmIndexReuse) {
|
|
112
124
|
const summary = warmIndexReuse.summary;
|
|
@@ -118,7 +130,7 @@ function buildInterpretation(run, contextStrategyComparisonV043, warmIndexReuse)
|
|
|
118
130
|
`Deterministic fake-agent correctness/token evidence is available for ${summary.agentCorrectnessAvailableCount} of ${summary.agentSideCount} task sides (correctness) and ${summary.agentTotalTokensAvailableCount} (total tokens); ` +
|
|
119
131
|
"fake-agent totals are simulated harness evidence, not real-model or provider measurements. " +
|
|
120
132
|
"Estimated context-token values are context-size estimates, not provider token usage. " +
|
|
121
|
-
freshnessSentence(warmIndexReuse),
|
|
133
|
+
freshnessSentence(warmIndexReuse) + " " + affectedNeighborhoodSentence(warmIndexReuse),
|
|
122
134
|
recommendedNextStep: run.status === "completed"
|
|
123
135
|
? "Review per-task measurements and cumulative component costs for each project; the variants are reported side by side and are not ranked."
|
|
124
136
|
: "Inspect unavailable metrics, warnings, and failures before drawing conclusions from this run.",
|
|
@@ -144,7 +156,7 @@ function buildInterpretation(run, contextStrategyComparisonV043, warmIndexReuse)
|
|
|
144
156
|
`${nonCompletedCounts ? `; outcome counts: ${nonCompletedCounts}` : ""}). ` +
|
|
145
157
|
`Token evidence status: ${campaign.tokenEvidenceStatus} (${summary.agentTotalTokensAvailableCount} of ${summary.agentSideCount} task sides have a total-token value). ` +
|
|
146
158
|
"Context estimated tokens remain a separate character-based context-size estimate and are never substituted for provider telemetry. " +
|
|
147
|
-
freshnessSentence(warmIndexReuse),
|
|
159
|
+
freshnessSentence(warmIndexReuse) + " " + affectedNeighborhoodSentence(warmIndexReuse),
|
|
148
160
|
recommendedNextStep: isInfrastructureComplete && isAgentEvidenceComplete
|
|
149
161
|
? "Review per-task correctness, token provenance, and cumulative component measurements."
|
|
150
162
|
: "Review infrastructure gaps (index build, raw/warm retrieval) separately from provider/agent limitations before drawing conclusions from this run.",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { MAX_REPORT_FRESHNESS_CHANGES, MAX_REPORT_FRESHNESS_UNRESOLVED, WARM_INDEX_REUSE_REPORT_SCHEMA_VERSION, } from "./warmIndexReuseReportModel.js";
|
|
1
|
+
import { MAX_REPORT_AFFECTED_EDGES, MAX_REPORT_AFFECTED_NODES, MAX_REPORT_AFFECTED_TASK_AMBIGUOUS, MAX_REPORT_AFFECTED_TASK_UNRESOLVED, MAX_REPORT_AFFECTED_WARNINGS, MAX_REPORT_FRESHNESS_CHANGES, MAX_REPORT_FRESHNESS_UNRESOLVED, WARM_INDEX_REUSE_REPORT_SCHEMA_VERSION, } from "./warmIndexReuseReportModel.js";
|
|
2
2
|
const WARM_INDEX_REUSE_PLUGIN_ID = "warm-index-reuse";
|
|
3
3
|
const COMMON_LIMITATIONS = [
|
|
4
4
|
"Context estimated-token counts use the character-based estimate and are not provider billing telemetry.",
|
|
@@ -30,6 +30,17 @@ const FRESHNESS_LIMITATIONS = [
|
|
|
30
30
|
"Freshness is observational evidence. It does not trigger reindexing, suppress warm retrieval, alter execution status, or alter provider/agent outcome status.",
|
|
31
31
|
"Freshness statuses: fresh means all represented indexed files were completely compared and their content identities still match; stale means a complete comparison confirmed at least one represented indexed file is modified or missing; partially-stale means at least one represented indexed file is confirmed changed but comparison evidence is incomplete; unknown means no confirmed change established staleness but evidence is insufficient to prove freshness. A task without an assessment is reported as not assessed, which is not the same as unknown.",
|
|
32
32
|
];
|
|
33
|
+
const AFFECTED_NEIGHBORHOOD_LIMITATIONS = [
|
|
34
|
+
"Affected-neighborhood evidence uses a one-hop baseline graph around confirmed changed indexed files/symbols. It is observational and does not alter retrieval or execution status.",
|
|
35
|
+
"A changed baseline symbol means the symbol was present in a confirmed changed indexed file; it is not proof that the symbol's own source text changed.",
|
|
36
|
+
"Reindex recommendation categories describe this bounded analysis only. They do not claim that reindexing is mandatory, that it guarantees correctness, that skipping it is universally safe, or that the whole repository is fresh.",
|
|
37
|
+
];
|
|
38
|
+
/** Fixed neutral wording per recommendation category. */
|
|
39
|
+
export const REINDEX_RECOMMENDATION_EXPLANATIONS = {
|
|
40
|
+
recommended: "Reindex recommended: the task has confirmed overlap with the affected one-hop baseline graph neighborhood.",
|
|
41
|
+
"not-indicated": "Reindex not indicated by this bounded analysis: complete evidence found no task-node overlap with the affected neighborhood.",
|
|
42
|
+
unknown: "Reindex recommendation unknown: evidence is incomplete or unavailable, so absence of observed overlap is not sufficient to conclude that the task is unaffected.",
|
|
43
|
+
};
|
|
33
44
|
const COST_MODEL = [
|
|
34
45
|
"Index construction is a one-time cost per benchmark project; task retrieval is the repeated warm cost.",
|
|
35
46
|
"Cold start: for the first task in a project, the cumulative warm component duration is the one-time index build plus the first retrieval.",
|
|
@@ -98,6 +109,7 @@ export function buildWarmIndexReuseReport(run) {
|
|
|
98
109
|
rawAgent: toReportAgent(rawEvidence),
|
|
99
110
|
warmAgent: toReportAgent(warmEvidence),
|
|
100
111
|
indexFreshness: toReportFreshness(execution.tasks[taskIndex].indexFreshness),
|
|
112
|
+
affectedNeighborhood: toReportAffectedNeighborhood(execution.tasks[taskIndex].affectedNeighborhood, task.warm),
|
|
101
113
|
};
|
|
102
114
|
}),
|
|
103
115
|
};
|
|
@@ -130,6 +142,7 @@ export function buildWarmIndexReuseReport(run) {
|
|
|
130
142
|
agent: agentIdentity,
|
|
131
143
|
agentCampaign,
|
|
132
144
|
indexFreshnessSummary: summarizeFreshness(projects.flatMap((project) => project.tasks)),
|
|
145
|
+
affectedNeighborhoodSummary: summarizeAffectedNeighborhood(projects.flatMap((project) => project.tasks)),
|
|
133
146
|
};
|
|
134
147
|
}
|
|
135
148
|
function boundedList(source, limit, map) {
|
|
@@ -162,6 +175,70 @@ function toReportFreshness(freshness) {
|
|
|
162
175
|
warnings: [...freshness.warnings],
|
|
163
176
|
};
|
|
164
177
|
}
|
|
178
|
+
/** Bounded list whose totals come from persisted counts, which can exceed the persisted item list. */
|
|
179
|
+
function boundedListWithTotal(source, totalCount, limit, map) {
|
|
180
|
+
const items = source.slice(0, limit).map(map);
|
|
181
|
+
const total = Math.max(totalCount, source.length);
|
|
182
|
+
return { totalCount: total, displayedCount: items.length, omittedCount: total - items.length, items };
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Presents a persisted affected-neighborhood assessment only: it never traverses, remaps seeds,
|
|
186
|
+
* recomputes overlap, or reads the graph, index, or filesystem. Numeric fields are the warm-side
|
|
187
|
+
* metric objects already calculated by the metric owner. Absent evidence stays null.
|
|
188
|
+
*/
|
|
189
|
+
function toReportAffectedNeighborhood(assessment, warm) {
|
|
190
|
+
if (!assessment)
|
|
191
|
+
return null;
|
|
192
|
+
const mapping = assessment.taskMapping;
|
|
193
|
+
return {
|
|
194
|
+
status: assessment.status,
|
|
195
|
+
freshnessStatus: assessment.freshnessStatus,
|
|
196
|
+
seedMappingStatus: assessment.seedMappingStatus,
|
|
197
|
+
graphEvidenceStatus: assessment.graphEvidenceStatus,
|
|
198
|
+
neighborhoodStatus: assessment.neighborhoodStatus,
|
|
199
|
+
metrics: {
|
|
200
|
+
changedFileCount: warm.changedFileCount,
|
|
201
|
+
changedSymbolCount: warm.changedSymbolCount,
|
|
202
|
+
affectedNodeCount: warm.affectedNodeCount,
|
|
203
|
+
affectedEdgeCount: warm.affectedEdgeCount,
|
|
204
|
+
taskOverlapCount: warm.taskOverlapCount,
|
|
205
|
+
taskOverlapPercent: warm.taskOverlapPercent,
|
|
206
|
+
},
|
|
207
|
+
taskMappingStatus: mapping.status,
|
|
208
|
+
resolvableTaskNodeCount: mapping.resolvableTaskNodeCount,
|
|
209
|
+
relationship: assessment.relationship,
|
|
210
|
+
reindexRecommendation: assessment.reindexRecommendation,
|
|
211
|
+
recommendationExplanation: REINDEX_RECOMMENDATION_EXPLANATIONS[assessment.reindexRecommendation],
|
|
212
|
+
affectedNodeIds: boundedList(assessment.affectedNodeIds, MAX_REPORT_AFFECTED_NODES, (id) => id),
|
|
213
|
+
participatingEdgeIds: boundedList(assessment.participatingEdgeIds, MAX_REPORT_AFFECTED_EDGES, (id) => id),
|
|
214
|
+
unresolvedTaskMappings: boundedListWithTotal(mapping.unresolved, mapping.unresolvedCount, MAX_REPORT_AFFECTED_TASK_UNRESOLVED, (entry) => ({ ...entry })),
|
|
215
|
+
ambiguousTaskSymbols: boundedListWithTotal(mapping.ambiguousSymbols, mapping.ambiguousCount, MAX_REPORT_AFFECTED_TASK_AMBIGUOUS, (entry) => ({
|
|
216
|
+
name: entry.name,
|
|
217
|
+
candidateNodeIds: [...entry.candidateNodeIds],
|
|
218
|
+
})),
|
|
219
|
+
warnings: boundedListWithTotal(assessment.warnings, assessment.warningCount, MAX_REPORT_AFFECTED_WARNINGS, (warning) => ({
|
|
220
|
+
code: warning.code,
|
|
221
|
+
message: warning.message,
|
|
222
|
+
})),
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
function summarizeAffectedNeighborhood(tasks) {
|
|
226
|
+
const assessed = tasks.flatMap((task) => (task.affectedNeighborhood ? [task.affectedNeighborhood] : []));
|
|
227
|
+
const count = (key, value) => assessed.filter((entry) => entry[key] === value).length;
|
|
228
|
+
return {
|
|
229
|
+
assessedTaskCount: assessed.length,
|
|
230
|
+
unassessedTaskCount: tasks.length - assessed.length,
|
|
231
|
+
completeAssessmentCount: count("status", "complete"),
|
|
232
|
+
partialAssessmentCount: count("status", "partial"),
|
|
233
|
+
unavailableAssessmentCount: count("status", "unavailable"),
|
|
234
|
+
relatedTaskCount: count("relationship", "related"),
|
|
235
|
+
unrelatedTaskCount: count("relationship", "unrelated"),
|
|
236
|
+
unknownRelationshipTaskCount: count("relationship", "unknown"),
|
|
237
|
+
recommendedReindexCount: count("reindexRecommendation", "recommended"),
|
|
238
|
+
notIndicatedReindexCount: count("reindexRecommendation", "not-indicated"),
|
|
239
|
+
unknownReindexRecommendationCount: count("reindexRecommendation", "unknown"),
|
|
240
|
+
};
|
|
241
|
+
}
|
|
165
242
|
function summarizeFreshness(tasks) {
|
|
166
243
|
const assessed = tasks.flatMap((task) => (task.indexFreshness ? [task.indexFreshness] : []));
|
|
167
244
|
const count = (status) => assessed.filter((freshness) => freshness.status === status).length;
|
|
@@ -242,10 +319,10 @@ function buildCampaignSummary(args) {
|
|
|
242
319
|
}
|
|
243
320
|
function buildLimitations(agent) {
|
|
244
321
|
if (agent.mode === "deterministic-fake") {
|
|
245
|
-
return [...COMMON_LIMITATIONS, ...FAKE_AGENT_LIMITATIONS, ...FRESHNESS_LIMITATIONS];
|
|
322
|
+
return [...COMMON_LIMITATIONS, ...FAKE_AGENT_LIMITATIONS, ...FRESHNESS_LIMITATIONS, ...AFFECTED_NEIGHBORHOOD_LIMITATIONS];
|
|
246
323
|
}
|
|
247
324
|
const providerLimitations = agent.id === "codex" ? CODEX_CAMPAIGN_LIMITATIONS : CLAUDE_CAMPAIGN_LIMITATIONS;
|
|
248
|
-
return [...COMMON_LIMITATIONS, ...providerLimitations, ...FRESHNESS_LIMITATIONS];
|
|
325
|
+
return [...COMMON_LIMITATIONS, ...providerLimitations, ...FRESHNESS_LIMITATIONS, ...AFFECTED_NEIGHBORHOOD_LIMITATIONS];
|
|
249
326
|
}
|
|
250
327
|
function toReportAgent(agent) {
|
|
251
328
|
if (!agent)
|
|
@@ -302,6 +302,7 @@ function renderWarmIndexReuseSection(lines, section) {
|
|
|
302
302
|
lines.push(fieldLine("Agent Correctness Available (task sides)", `${section.summary.agentCorrectnessAvailableCount} of ${section.summary.agentSideCount}`));
|
|
303
303
|
lines.push(fieldLine("Agent Token Totals Available (task sides)", `${section.summary.agentTotalTokensAvailableCount} of ${section.summary.agentSideCount}`));
|
|
304
304
|
renderFreshnessSummary(lines, section);
|
|
305
|
+
renderAffectedNeighborhoodSummary(lines, section);
|
|
305
306
|
if (section.agentCampaign) {
|
|
306
307
|
renderCampaignSection(lines, section.agentCampaign);
|
|
307
308
|
}
|
|
@@ -326,6 +327,7 @@ function renderWarmIndexReuseSection(lines, section) {
|
|
|
326
327
|
["Raw Status", task.rawStatus],
|
|
327
328
|
["Warm Status", task.warmStatus],
|
|
328
329
|
["Index Freshness Status", task.indexFreshness ? task.indexFreshness.status : "not assessed"],
|
|
330
|
+
["Affected Neighborhood Status", task.affectedNeighborhood ? task.affectedNeighborhood.status : "not assessed"],
|
|
329
331
|
["Raw Context Characters", formatWarmMetric(task.raw.contextCharacters)],
|
|
330
332
|
["Warm Context Characters", formatWarmMetric(task.warm.contextCharacters)],
|
|
331
333
|
["Raw Estimated Context Tokens (estimate)", formatWarmMetric(task.raw.contextEstimatedTokens)],
|
|
@@ -350,6 +352,7 @@ function renderWarmIndexReuseSection(lines, section) {
|
|
|
350
352
|
lines.push(fieldLine(label, value));
|
|
351
353
|
}
|
|
352
354
|
renderTaskFreshnessDetail(lines, task.indexFreshness);
|
|
355
|
+
renderTaskAffectedNeighborhoodDetail(lines, task.affectedNeighborhood);
|
|
353
356
|
}
|
|
354
357
|
});
|
|
355
358
|
}
|
|
@@ -371,6 +374,87 @@ function renderFreshnessSummary(lines, section) {
|
|
|
371
374
|
lines.push(fieldLine("Partially-Stale Tasks", summary.partiallyStaleTaskCount));
|
|
372
375
|
lines.push(fieldLine("Unknown Tasks", summary.unknownTaskCount));
|
|
373
376
|
}
|
|
377
|
+
function renderAffectedNeighborhoodSummary(lines, section) {
|
|
378
|
+
// Reports serialized before v0.6.1 lack the summary; render them as having no assessment.
|
|
379
|
+
const summary = section.affectedNeighborhoodSummary ?? {
|
|
380
|
+
assessedTaskCount: 0,
|
|
381
|
+
unassessedTaskCount: section.summary.taskCount,
|
|
382
|
+
completeAssessmentCount: 0,
|
|
383
|
+
partialAssessmentCount: 0,
|
|
384
|
+
unavailableAssessmentCount: 0,
|
|
385
|
+
relatedTaskCount: 0,
|
|
386
|
+
unrelatedTaskCount: 0,
|
|
387
|
+
unknownRelationshipTaskCount: 0,
|
|
388
|
+
recommendedReindexCount: 0,
|
|
389
|
+
notIndicatedReindexCount: 0,
|
|
390
|
+
unknownReindexRecommendationCount: 0,
|
|
391
|
+
};
|
|
392
|
+
pushSection(lines, "Affected Neighborhood Summary");
|
|
393
|
+
lines.push(fieldLine("Assessed Tasks", summary.assessedTaskCount));
|
|
394
|
+
lines.push(fieldLine("Unassessed Tasks", summary.unassessedTaskCount));
|
|
395
|
+
lines.push(fieldLine("Complete Assessments", summary.completeAssessmentCount));
|
|
396
|
+
lines.push(fieldLine("Partial Assessments", summary.partialAssessmentCount));
|
|
397
|
+
lines.push(fieldLine("Unavailable Assessments", summary.unavailableAssessmentCount));
|
|
398
|
+
lines.push(fieldLine("Related Tasks", summary.relatedTaskCount));
|
|
399
|
+
lines.push(fieldLine("Unrelated Tasks", summary.unrelatedTaskCount));
|
|
400
|
+
lines.push(fieldLine("Unknown-Relationship Tasks", summary.unknownRelationshipTaskCount));
|
|
401
|
+
lines.push(fieldLine("Reindex Recommended", summary.recommendedReindexCount));
|
|
402
|
+
lines.push(fieldLine("Reindex Not Indicated", summary.notIndicatedReindexCount));
|
|
403
|
+
lines.push(fieldLine("Reindex Recommendation Unknown", summary.unknownReindexRecommendationCount));
|
|
404
|
+
}
|
|
405
|
+
/** Renders the persisted affected-neighborhood evidence; numeric lines come from the metric owner. */
|
|
406
|
+
function renderTaskAffectedNeighborhoodDetail(lines, evidence) {
|
|
407
|
+
if (!evidence)
|
|
408
|
+
return;
|
|
409
|
+
lines.push(fieldLine("Affected Neighborhood Relationship", evidence.relationship));
|
|
410
|
+
lines.push(fieldLine("Reindex Recommendation", evidence.reindexRecommendation));
|
|
411
|
+
lines.push(fieldLine("Reindex Recommendation Explanation", evidence.recommendationExplanation));
|
|
412
|
+
lines.push(fieldLine("Affected Neighborhood Freshness Status", evidence.freshnessStatus));
|
|
413
|
+
lines.push(fieldLine("Seed Mapping Status", evidence.seedMappingStatus));
|
|
414
|
+
lines.push(fieldLine("Graph Evidence Status", evidence.graphEvidenceStatus));
|
|
415
|
+
lines.push(fieldLine("Neighborhood Status", evidence.neighborhoodStatus));
|
|
416
|
+
lines.push(fieldLine("Task Mapping Status", evidence.taskMappingStatus));
|
|
417
|
+
lines.push(fieldLine("Resolvable Task Nodes", evidence.resolvableTaskNodeCount));
|
|
418
|
+
lines.push(fieldLine("Changed Indexed Files", formatWarmMetric(evidence.metrics.changedFileCount)));
|
|
419
|
+
lines.push(fieldLine("Changed Baseline Symbols", formatWarmMetric(evidence.metrics.changedSymbolCount)));
|
|
420
|
+
lines.push(fieldLine("Affected Graph Nodes", formatWarmMetric(evidence.metrics.affectedNodeCount)));
|
|
421
|
+
lines.push(fieldLine("Affected Graph Edges", formatWarmMetric(evidence.metrics.affectedEdgeCount)));
|
|
422
|
+
lines.push(fieldLine("Task-Overlap Nodes", formatWarmMetric(evidence.metrics.taskOverlapCount)));
|
|
423
|
+
lines.push(fieldLine("Task-Overlap Percent", formatWarmMetric(evidence.metrics.taskOverlapPercent)));
|
|
424
|
+
lines.push("Affected Node IDs:");
|
|
425
|
+
pushFreshnessBoundedNote(lines, evidence.affectedNodeIds);
|
|
426
|
+
if (evidence.affectedNodeIds.items.length === 0)
|
|
427
|
+
lines.push("- none");
|
|
428
|
+
for (const id of evidence.affectedNodeIds.items)
|
|
429
|
+
lines.push(`- ${sanitizeScalar(id)}`);
|
|
430
|
+
lines.push("Participating Edge IDs:");
|
|
431
|
+
pushFreshnessBoundedNote(lines, evidence.participatingEdgeIds);
|
|
432
|
+
if (evidence.participatingEdgeIds.items.length === 0)
|
|
433
|
+
lines.push("- none");
|
|
434
|
+
for (const id of evidence.participatingEdgeIds.items)
|
|
435
|
+
lines.push(`- ${sanitizeScalar(id)}`);
|
|
436
|
+
lines.push("Unresolved Task Mappings:");
|
|
437
|
+
pushFreshnessBoundedNote(lines, evidence.unresolvedTaskMappings);
|
|
438
|
+
if (evidence.unresolvedTaskMappings.items.length === 0)
|
|
439
|
+
lines.push("- none");
|
|
440
|
+
for (const entry of evidence.unresolvedTaskMappings.items) {
|
|
441
|
+
lines.push(`- ${sanitizeScalar(entry.subject)} ${sanitizeScalar(entry.name)} [${sanitizeScalar(entry.reason)}]`);
|
|
442
|
+
}
|
|
443
|
+
lines.push("Ambiguous Task Symbols:");
|
|
444
|
+
pushFreshnessBoundedNote(lines, evidence.ambiguousTaskSymbols);
|
|
445
|
+
if (evidence.ambiguousTaskSymbols.items.length === 0)
|
|
446
|
+
lines.push("- none");
|
|
447
|
+
for (const entry of evidence.ambiguousTaskSymbols.items) {
|
|
448
|
+
lines.push(`- ${sanitizeScalar(entry.name)}: ${entry.candidateNodeIds.map((id) => sanitizeScalar(id)).join(", ")}`);
|
|
449
|
+
}
|
|
450
|
+
lines.push("Affected Neighborhood Warnings:");
|
|
451
|
+
pushFreshnessBoundedNote(lines, evidence.warnings);
|
|
452
|
+
if (evidence.warnings.items.length === 0)
|
|
453
|
+
lines.push("- none");
|
|
454
|
+
for (const warning of evidence.warnings.items) {
|
|
455
|
+
lines.push(`- [${sanitizeScalar(warning.code)}] ${sanitizeScalar(warning.message)}`);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
374
458
|
function pushFreshnessBoundedNote(lines, list) {
|
|
375
459
|
if (list.omittedCount > 0) {
|
|
376
460
|
lines.push(`Displayed: ${list.displayedCount} of ${list.totalCount}`);
|
|
@@ -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;
|