pi-harness-runtime 1.1.454 → 1.1.455

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 (69) hide show
  1. package/AGENTS.md +4 -1
  2. package/CHANGELOG.md +30 -0
  3. package/index.ts +11 -2
  4. package/package.json +1 -1
  5. package/packages/agent-policy/package.json +1 -1
  6. package/packages/auth/package.json +1 -1
  7. package/packages/autonomous-runtime/package.json +1 -1
  8. package/packages/checkpoint/package.json +1 -1
  9. package/packages/clipboard/package.json +1 -1
  10. package/packages/codex-watcher/package.json +1 -1
  11. package/packages/config-capture/package.json +1 -1
  12. package/packages/contracts/package.json +1 -1
  13. package/packages/cookie-sanitizer/package.json +1 -1
  14. package/packages/evaluation-engine/package.json +1 -1
  15. package/packages/event-bus/package.json +1 -1
  16. package/packages/event-store/package.json +1 -1
  17. package/packages/experience-replay/package.json +1 -1
  18. package/packages/file-copy-helper/package.json +1 -1
  19. package/packages/intent-analyzer/package.json +1 -1
  20. package/packages/jev-judge/dist/index.d.ts +3 -1
  21. package/packages/jev-judge/dist/index.d.ts.map +1 -1
  22. package/packages/jev-judge/dist/index.js +3 -1
  23. package/packages/jev-judge/dist/index.js.map +1 -1
  24. package/packages/jev-judge/dist/validation-plan.d.ts +29 -0
  25. package/packages/jev-judge/dist/validation-plan.d.ts.map +1 -1
  26. package/packages/jev-judge/dist/validation-plan.js +58 -2
  27. package/packages/jev-judge/dist/validation-plan.js.map +1 -1
  28. package/packages/jev-judge/dist/workload.d.ts +126 -0
  29. package/packages/jev-judge/dist/workload.d.ts.map +1 -0
  30. package/packages/jev-judge/dist/workload.js +271 -0
  31. package/packages/jev-judge/dist/workload.js.map +1 -0
  32. package/packages/jev-judge/package.json +1 -1
  33. package/packages/jev-judge/src/index.ts +28 -0
  34. package/packages/jev-judge/src/validation-plan.ts +72 -4
  35. package/packages/jev-judge/src/workload.ts +336 -0
  36. package/packages/knowledge-retrieval/package.json +1 -1
  37. package/packages/learning-engine/package.json +1 -1
  38. package/packages/logging/package.json +1 -1
  39. package/packages/memory-engine/package.json +1 -1
  40. package/packages/notification/package.json +1 -1
  41. package/packages/paperclip-integration/package.json +1 -1
  42. package/packages/performance-optimizer/package.json +1 -1
  43. package/packages/privilege-broker/package.json +1 -1
  44. package/packages/prompt-compiler/package.json +1 -1
  45. package/packages/providers/package.json +1 -1
  46. package/packages/qdrant-skills/package.json +1 -1
  47. package/packages/quota-manager/package.json +1 -1
  48. package/packages/relay/package.json +1 -1
  49. package/packages/skill-mcp-client/package.json +1 -1
  50. package/packages/skills/package.json +1 -1
  51. package/packages/todo-bd-sync/package.json +1 -1
  52. package/packages/token-estimation/package.json +1 -1
  53. package/packages/tooling-preference/package.json +1 -1
  54. package/packages/trajectory/package.json +1 -1
  55. package/packages/tui/package.json +1 -1
  56. package/packages/two-phase-review/dist/rules.d.ts.map +1 -1
  57. package/packages/two-phase-review/dist/rules.js +2 -0
  58. package/packages/two-phase-review/dist/rules.js.map +1 -1
  59. package/packages/two-phase-review/dist/validation.d.ts +2 -0
  60. package/packages/two-phase-review/dist/validation.d.ts.map +1 -1
  61. package/packages/two-phase-review/dist/validation.js +6 -1
  62. package/packages/two-phase-review/dist/validation.js.map +1 -1
  63. package/packages/two-phase-review/package.json +1 -1
  64. package/packages/two-phase-review/src/rules.ts +1 -0
  65. package/packages/two-phase-review/src/validation.ts +8 -1
  66. package/packages/types/package.json +1 -1
  67. package/packages/webmcp-tester/package.json +1 -1
  68. package/packages/wiki/package.json +1 -1
  69. package/packages/write-review/package.json +1 -1
@@ -0,0 +1,126 @@
1
+ /**
2
+ * The workload of a job is a fact, not a phrase.
3
+ *
4
+ * A request that says "large refactor" is not a large job, and a request
5
+ * that says "10,000 records" is a large job even when the phrase is
6
+ * missing. The assessment reads the facts that are really available - a
7
+ * declared record count, a measured scope, a declared tier cost, a
8
+ * measured cost per record - and it reports what it decided and why.
9
+ *
10
+ * A job without any fact is `unknown`, never `small`: the difference
11
+ * decides whether a pilot is needed, and a job of unknown size is not a
12
+ * job that may skip the pilot.
13
+ *
14
+ * The assessment is deterministic. It never asks a model what the size of
15
+ * a job is, because a model cannot count the records of a repository.
16
+ */
17
+ import type { BatchPlan } from "./batch-plan.js";
18
+ /** A job of this many records is a large job. */
19
+ export declare const LARGE_RECORD_COUNT = 1000;
20
+ /** A job of this many records is at least a medium job. */
21
+ export declare const MEDIUM_RECORD_COUNT = 100;
22
+ /** A change of this many files is a large job. */
23
+ export declare const LARGE_FILE_COUNT = 50;
24
+ /** A change of this many files is at least a medium job. */
25
+ export declare const MEDIUM_FILE_COUNT = 10;
26
+ /** A change with this many call sites is a large job. */
27
+ export declare const LARGE_CALL_SITES = 200;
28
+ /** A change with this many call sites is at least a medium job. */
29
+ export declare const MEDIUM_CALL_SITES = 50;
30
+ /** A job that runs this long is a large job. */
31
+ export declare const LARGE_DURATION_MS = 600000;
32
+ /** A job that runs this long is at least a medium job. */
33
+ export declare const MEDIUM_DURATION_MS = 60000;
34
+ /** A change with this many migration files is a large job. */
35
+ export declare const LARGE_MIGRATION_FILES = 20;
36
+ /** A change with this many migration files is at least a medium job. */
37
+ export declare const MEDIUM_MIGRATION_FILES = 5;
38
+ /** The size of the representative pilot of a large job. */
39
+ export declare const PILOT_RECORDS = 100;
40
+ /** The size of a bounded batch of a large job. */
41
+ export declare const MAX_BATCH_RECORDS = 500;
42
+ /** The highest number of batches that may run at the same time. */
43
+ export declare const MAX_CONCURRENCY = 2;
44
+ export type WorkloadSize = "unknown" | "small" | "medium" | "large";
45
+ /** The scope that was measured in the repository. */
46
+ export interface WorkloadScopeFacts {
47
+ fileCount?: number;
48
+ callSiteCount?: number;
49
+ testFileCount?: number;
50
+ migrationFileCount?: number;
51
+ }
52
+ /** A tier of the declared validation plan. */
53
+ export interface WorkloadTierFacts {
54
+ datasetSize?: number;
55
+ expectedDurationMs?: number;
56
+ }
57
+ export interface WorkloadInput {
58
+ /** The request of the task. A count in it is a declaration. */
59
+ requestText?: string;
60
+ /** The scope that was measured in the repository. */
61
+ scope?: WorkloadScopeFacts;
62
+ /** The tiers of the declared validation plan. */
63
+ tiers?: WorkloadTierFacts[];
64
+ /** The measured cost of one record. */
65
+ msPerRecord?: number;
66
+ /** The batches write to disjoint destinations. */
67
+ partitionSafe?: boolean;
68
+ }
69
+ export interface WorkloadAssessment {
70
+ /** The size of the job, decided from the facts. */
71
+ size: WorkloadSize;
72
+ /** The job is a large job. */
73
+ isLarge: boolean;
74
+ /** The facts that decided the size, in plain words. */
75
+ reasons: string[];
76
+ /** The number of records of the job, when it is known. */
77
+ recordCount?: number;
78
+ /** The number of changed files, when it was measured. */
79
+ fileCount?: number;
80
+ /** The number of call sites, when it was measured. */
81
+ callSiteCount?: number;
82
+ /** The measured or declared duration of the job. */
83
+ estimatedDurationMs?: number;
84
+ /** The size of the representative pilot, when the job needs one. */
85
+ pilotRecords?: number;
86
+ /** The size of a bounded batch, when the job has a record count. */
87
+ batchRecords?: number;
88
+ /** How many batches may run at the same time. */
89
+ concurrency: number;
90
+ /** No fact was available, so the size is unknown. */
91
+ unknown: boolean;
92
+ }
93
+ /**
94
+ * The largest record count that the request declares.
95
+ *
96
+ * "10,000 records", "500 rows", "1.5M items" and "250 users" are
97
+ * declarations of a count. The largest one wins, because the request may
98
+ * also name the size of a batch or of a sample.
99
+ */
100
+ export declare function declaredRecordCount(text: string | undefined): number | undefined;
101
+ /**
102
+ * The workload of a job, decided from the facts.
103
+ *
104
+ * The order of the thresholds is the order of the certainty: a declared
105
+ * count of records is the strongest fact, a measured scope is the next
106
+ * one, and a duration that is derived from a measured cost per record is
107
+ * the weakest.
108
+ */
109
+ export declare function assessWorkload(input: WorkloadInput): WorkloadAssessment;
110
+ /**
111
+ * A one line description of the assessment, for the status line and the
112
+ * instruction of the agent.
113
+ */
114
+ export declare function describeWorkload(assessment: WorkloadAssessment): string;
115
+ /**
116
+ * The batch plan of an assessed job.
117
+ *
118
+ * The plan carries only what was measured: without a measured cost per
119
+ * record the estimate stays unknown, and the batches stay bounded.
120
+ */
121
+ export declare function workloadBatchPlan(assessment: WorkloadAssessment, extra?: {
122
+ budgetMinutes?: number;
123
+ maxAttempts?: number;
124
+ msPerRecord?: number;
125
+ }): BatchPlan;
126
+ //# sourceMappingURL=workload.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workload.d.ts","sourceRoot":"","sources":["../src/workload.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,271 @@
1
+ /**
2
+ * The workload of a job is a fact, not a phrase.
3
+ *
4
+ * A request that says "large refactor" is not a large job, and a request
5
+ * that says "10,000 records" is a large job even when the phrase is
6
+ * missing. The assessment reads the facts that are really available - a
7
+ * declared record count, a measured scope, a declared tier cost, a
8
+ * measured cost per record - and it reports what it decided and why.
9
+ *
10
+ * A job without any fact is `unknown`, never `small`: the difference
11
+ * decides whether a pilot is needed, and a job of unknown size is not a
12
+ * job that may skip the pilot.
13
+ *
14
+ * The assessment is deterministic. It never asks a model what the size of
15
+ * a job is, because a model cannot count the records of a repository.
16
+ */
17
+ /** A job of this many records is a large job. */
18
+ export const LARGE_RECORD_COUNT = 1_000;
19
+ /** A job of this many records is at least a medium job. */
20
+ export const MEDIUM_RECORD_COUNT = 100;
21
+ /** A change of this many files is a large job. */
22
+ export const LARGE_FILE_COUNT = 50;
23
+ /** A change of this many files is at least a medium job. */
24
+ export const MEDIUM_FILE_COUNT = 10;
25
+ /** A change with this many call sites is a large job. */
26
+ export const LARGE_CALL_SITES = 200;
27
+ /** A change with this many call sites is at least a medium job. */
28
+ export const MEDIUM_CALL_SITES = 50;
29
+ /** A job that runs this long is a large job. */
30
+ export const LARGE_DURATION_MS = 600_000;
31
+ /** A job that runs this long is at least a medium job. */
32
+ export const MEDIUM_DURATION_MS = 60_000;
33
+ /** A change with this many migration files is a large job. */
34
+ export const LARGE_MIGRATION_FILES = 20;
35
+ /** A change with this many migration files is at least a medium job. */
36
+ export const MEDIUM_MIGRATION_FILES = 5;
37
+ /** The size of the representative pilot of a large job. */
38
+ export const PILOT_RECORDS = 100;
39
+ /** The size of a bounded batch of a large job. */
40
+ export const MAX_BATCH_RECORDS = 500;
41
+ /** The highest number of batches that may run at the same time. */
42
+ export const MAX_CONCURRENCY = 2;
43
+ /** The text of a request is only scanned up to this many characters. */
44
+ const MAX_SCANNED_TEXT = 20_000;
45
+ /** A declared count above this is not a count, it is a typo. */
46
+ const MAX_DECLARED_RECORDS = 1e12;
47
+ /**
48
+ * The units that name a record. A file is not a record: the number of
49
+ * files is a measured fact of the repository, not a declaration of the
50
+ * user.
51
+ */
52
+ const RECORD_UNITS = "records?|rows?|items?|entries|documents?|messages?|events?|tickets?|samples?|users?|customers?|orders?|invoices?|transactions?";
53
+ /**
54
+ * The count of a record declaration.
55
+ *
56
+ * The pattern is linear: a digit class, an optional fraction, an optional
57
+ * multiplier and a unit. There is no nested repetition, so a hostile
58
+ * request cannot make the scan slow.
59
+ */
60
+ const COUNT_PATTERN = new RegExp(`(\\d[\\d,]*)(?:\\.(\\d+))?\\s*(k|m)?\\s*(${RECORD_UNITS})\\b`, "gi");
61
+ function positiveNumber(value) {
62
+ if (value === undefined)
63
+ return undefined;
64
+ if (!Number.isFinite(value))
65
+ return undefined;
66
+ if (value <= 0)
67
+ return undefined;
68
+ return value;
69
+ }
70
+ function maxOf(values) {
71
+ const known = values.map(positiveNumber).filter((v) => v !== undefined);
72
+ if (known.length === 0)
73
+ return undefined;
74
+ return Math.max(...known);
75
+ }
76
+ /**
77
+ * The largest record count that the request declares.
78
+ *
79
+ * "10,000 records", "500 rows", "1.5M items" and "250 users" are
80
+ * declarations of a count. The largest one wins, because the request may
81
+ * also name the size of a batch or of a sample.
82
+ */
83
+ export function declaredRecordCount(text) {
84
+ if (!text)
85
+ return undefined;
86
+ const scan = text.length > MAX_SCANNED_TEXT ? text.slice(0, MAX_SCANNED_TEXT) : text;
87
+ COUNT_PATTERN.lastIndex = 0;
88
+ let largest;
89
+ let match = COUNT_PATTERN.exec(scan);
90
+ while (match !== null) {
91
+ const integerPart = Number.parseInt((match[1] ?? "0").replace(/,/g, ""), 10);
92
+ const fractionDigits = match[2] ?? "";
93
+ const fraction = fractionDigits.length > 0 ? Number.parseInt(fractionDigits, 10) / 10 ** fractionDigits.length : 0;
94
+ const suffix = match[3]?.toLowerCase();
95
+ let multiplier = 1;
96
+ if (suffix === "m")
97
+ multiplier = 1_000_000;
98
+ else if (suffix === "k")
99
+ multiplier = 1_000;
100
+ const value = (integerPart + fraction) * multiplier;
101
+ if (Number.isFinite(value) && value > 0 && value <= MAX_DECLARED_RECORDS) {
102
+ if (largest === undefined || value > largest)
103
+ largest = value;
104
+ }
105
+ match = COUNT_PATTERN.exec(scan);
106
+ }
107
+ return largest;
108
+ }
109
+ /**
110
+ * The workload of a job, decided from the facts.
111
+ *
112
+ * The order of the thresholds is the order of the certainty: a declared
113
+ * count of records is the strongest fact, a measured scope is the next
114
+ * one, and a duration that is derived from a measured cost per record is
115
+ * the weakest.
116
+ */
117
+ export function assessWorkload(input) {
118
+ const reasons = [];
119
+ const declared = declaredRecordCount(input.requestText);
120
+ const tierRecords = maxOf((input.tiers ?? []).map((tier) => tier.datasetSize));
121
+ const tierDuration = maxOf((input.tiers ?? []).map((tier) => tier.expectedDurationMs));
122
+ const recordCount = maxOf([declared, tierRecords]);
123
+ const fileCount = positiveNumber(input.scope?.fileCount);
124
+ const callSiteCount = positiveNumber(input.scope?.callSiteCount);
125
+ const migrationFileCount = positiveNumber(input.scope?.migrationFileCount);
126
+ const msPerRecord = positiveNumber(input.msPerRecord);
127
+ const estimatedDurationMs = msPerRecord !== undefined && recordCount !== undefined ? Math.round(msPerRecord * recordCount) : undefined;
128
+ const declaredDuration = maxOf([tierDuration, estimatedDurationMs]);
129
+ let size = "unknown";
130
+ if (recordCount !== undefined && recordCount >= LARGE_RECORD_COUNT) {
131
+ size = "large";
132
+ reasons.push(`${recordCount} record(s) is at least ${LARGE_RECORD_COUNT}`);
133
+ }
134
+ if (fileCount !== undefined && fileCount >= LARGE_FILE_COUNT) {
135
+ size = "large";
136
+ reasons.push(`${fileCount} changed file(s) is at least ${LARGE_FILE_COUNT}`);
137
+ }
138
+ if (callSiteCount !== undefined && callSiteCount >= LARGE_CALL_SITES) {
139
+ size = "large";
140
+ reasons.push(`${callSiteCount} call site(s) is at least ${LARGE_CALL_SITES}`);
141
+ }
142
+ if (migrationFileCount !== undefined && migrationFileCount >= LARGE_MIGRATION_FILES) {
143
+ size = "large";
144
+ reasons.push(`${migrationFileCount} migration file(s) is at least ${LARGE_MIGRATION_FILES}`);
145
+ }
146
+ if (declaredDuration !== undefined && declaredDuration >= LARGE_DURATION_MS) {
147
+ size = "large";
148
+ reasons.push(`a runtime of ${Math.round(declaredDuration / 1000)}s is at least ${LARGE_DURATION_MS / 1000}s`);
149
+ }
150
+ if (size !== "large") {
151
+ if (recordCount !== undefined && recordCount >= MEDIUM_RECORD_COUNT) {
152
+ size = "medium";
153
+ reasons.push(`${recordCount} record(s) is at least ${MEDIUM_RECORD_COUNT}`);
154
+ }
155
+ if (fileCount !== undefined && fileCount >= MEDIUM_FILE_COUNT) {
156
+ size = "medium";
157
+ reasons.push(`${fileCount} changed file(s) is at least ${MEDIUM_FILE_COUNT}`);
158
+ }
159
+ if (callSiteCount !== undefined && callSiteCount >= MEDIUM_CALL_SITES) {
160
+ size = "medium";
161
+ reasons.push(`${callSiteCount} call site(s) is at least ${MEDIUM_CALL_SITES}`);
162
+ }
163
+ if (migrationFileCount !== undefined && migrationFileCount >= MEDIUM_MIGRATION_FILES) {
164
+ size = "medium";
165
+ reasons.push(`${migrationFileCount} migration file(s) is at least ${MEDIUM_MIGRATION_FILES}`);
166
+ }
167
+ if (declaredDuration !== undefined && declaredDuration >= MEDIUM_DURATION_MS) {
168
+ size = "medium";
169
+ reasons.push(`a runtime of ${Math.round(declaredDuration / 1000)}s is at least ${MEDIUM_DURATION_MS / 1000}s`);
170
+ }
171
+ }
172
+ const hasFact = recordCount !== undefined ||
173
+ fileCount !== undefined ||
174
+ callSiteCount !== undefined ||
175
+ migrationFileCount !== undefined ||
176
+ declaredDuration !== undefined;
177
+ if (size === "unknown" && hasFact) {
178
+ size = "small";
179
+ reasons.push("every measured fact is below the medium threshold");
180
+ }
181
+ else if (size === "unknown") {
182
+ reasons.push("no fact about the size of the job was available");
183
+ }
184
+ // The pilot of a large job is the representative sample. It is never
185
+ // bigger than the job itself, and it needs a record count: a pilot
186
+ // without a size is not a pilot.
187
+ let pilotRecords;
188
+ if (size === "large") {
189
+ if (recordCount !== undefined) {
190
+ pilotRecords = Math.min(PILOT_RECORDS, recordCount);
191
+ reasons.push(`the pilot covers ${pilotRecords} record(s)`);
192
+ }
193
+ else {
194
+ reasons.push("the job is large by scope, but no record count is known, so the pilot has no size");
195
+ }
196
+ }
197
+ const batchRecords = recordCount !== undefined ? Math.min(MAX_BATCH_RECORDS, recordCount) : undefined;
198
+ // Concurrent writers are only allowed when the batches provably write
199
+ // to disjoint destinations. Everything else runs one batch at a time.
200
+ const partitionSafe = input.partitionSafe === true;
201
+ const concurrency = size === "large" && partitionSafe ? MAX_CONCURRENCY : 1;
202
+ if (size === "large") {
203
+ reasons.push(partitionSafe
204
+ ? `the batches write to disjoint destinations, so ${MAX_CONCURRENCY} of them may run at once`
205
+ : "the batches may share a destination, so they run one at a time");
206
+ }
207
+ const assessment = {
208
+ size,
209
+ isLarge: size === "large",
210
+ reasons,
211
+ concurrency,
212
+ unknown: size === "unknown",
213
+ };
214
+ if (recordCount !== undefined)
215
+ assessment.recordCount = recordCount;
216
+ if (fileCount !== undefined)
217
+ assessment.fileCount = fileCount;
218
+ if (callSiteCount !== undefined)
219
+ assessment.callSiteCount = callSiteCount;
220
+ if (declaredDuration !== undefined)
221
+ assessment.estimatedDurationMs = declaredDuration;
222
+ if (pilotRecords !== undefined)
223
+ assessment.pilotRecords = pilotRecords;
224
+ if (batchRecords !== undefined)
225
+ assessment.batchRecords = batchRecords;
226
+ return assessment;
227
+ }
228
+ /**
229
+ * A one line description of the assessment, for the status line and the
230
+ * instruction of the agent.
231
+ */
232
+ export function describeWorkload(assessment) {
233
+ const parts = [`workload ${assessment.size}`];
234
+ if (assessment.recordCount !== undefined)
235
+ parts.push(`${assessment.recordCount} record(s)`);
236
+ if (assessment.fileCount !== undefined)
237
+ parts.push(`${assessment.fileCount} file(s)`);
238
+ if (assessment.callSiteCount !== undefined)
239
+ parts.push(`${assessment.callSiteCount} call site(s)`);
240
+ if (assessment.pilotRecords !== undefined)
241
+ parts.push(`pilot ${assessment.pilotRecords}`);
242
+ if (assessment.batchRecords !== undefined)
243
+ parts.push(`batch ${assessment.batchRecords}`);
244
+ parts.push(`concurrency ${assessment.concurrency}`);
245
+ if (assessment.estimatedDurationMs !== undefined) {
246
+ parts.push(`~${Math.round(assessment.estimatedDurationMs / 1000)}s`);
247
+ }
248
+ return parts.join(" | ");
249
+ }
250
+ /**
251
+ * The batch plan of an assessed job.
252
+ *
253
+ * The plan carries only what was measured: without a measured cost per
254
+ * record the estimate stays unknown, and the batches stay bounded.
255
+ */
256
+ export function workloadBatchPlan(assessment, extra = {}) {
257
+ const plan = {
258
+ totalRecords: assessment.recordCount ?? 0,
259
+ pilotRecords: assessment.pilotRecords ?? 0,
260
+ maxBatchRecords: assessment.batchRecords ?? MAX_BATCH_RECORDS,
261
+ maxConcurrentBatches: assessment.concurrency,
262
+ };
263
+ if (extra.budgetMinutes !== undefined)
264
+ plan.budgetMinutes = extra.budgetMinutes;
265
+ if (extra.maxAttempts !== undefined)
266
+ plan.maxAttempts = extra.maxAttempts;
267
+ if (extra.msPerRecord !== undefined)
268
+ plan.msPerRecord = extra.msPerRecord;
269
+ return plan;
270
+ }
271
+ //# sourceMappingURL=workload.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workload.js","sourceRoot":"","sources":["../src/workload.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/jev-judge",
3
- "version": "1.1.454",
3
+ "version": "1.1.455",
4
4
  "description": "Jev Judge - Structured decision model for agent routing, E2E testing, and automated decisions",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -759,6 +759,8 @@ export {
759
759
  formatDuration,
760
760
  summarizeValidationState,
761
761
  validationBudgetStatus,
762
+ planWorkload,
763
+ planHasPilot,
762
764
  } from "./validation-plan.js";
763
765
  export type {
764
766
  ValidationPlan,
@@ -818,6 +820,32 @@ export type {
818
820
  BatchStatus,
819
821
  } from "./batch-plan.js";
820
822
 
823
+ // Export the workload of a job, decided from the facts of the task
824
+ export {
825
+ assessWorkload,
826
+ declaredRecordCount,
827
+ describeWorkload,
828
+ workloadBatchPlan,
829
+ LARGE_RECORD_COUNT,
830
+ MEDIUM_RECORD_COUNT,
831
+ LARGE_FILE_COUNT,
832
+ MEDIUM_FILE_COUNT,
833
+ LARGE_CALL_SITES,
834
+ MEDIUM_CALL_SITES,
835
+ LARGE_DURATION_MS,
836
+ MEDIUM_DURATION_MS,
837
+ PILOT_RECORDS,
838
+ MAX_BATCH_RECORDS,
839
+ MAX_CONCURRENCY,
840
+ } from "./workload.js";
841
+ export type {
842
+ WorkloadAssessment,
843
+ WorkloadInput,
844
+ WorkloadScopeFacts,
845
+ WorkloadSize,
846
+ WorkloadTierFacts,
847
+ } from "./workload.js";
848
+
821
849
  // Export the validation tie-breaker (typed verdict about one item)
822
850
  export {
823
851
  tieBreakValidation,
@@ -23,6 +23,7 @@
23
23
  */
24
24
 
25
25
  import type { TodoItem } from "./todo-provider.js";
26
+ import { assessWorkload, describeWorkload, type WorkloadAssessment, type WorkloadTierFacts } from "./workload.js";
26
27
 
27
28
  /**
28
29
  * Which side of the ladder a tier sits on.
@@ -101,6 +102,17 @@ export interface ValidationState {
101
102
  budgetMinutes?: number;
102
103
  /** True when the plan requires a final tier but does not declare one. */
103
104
  planIncomplete: boolean;
105
+ /**
106
+ * True when the plan declares a large job but no measured pilot.
107
+ *
108
+ * A large job is accepted by the scale-up of a representative pilot,
109
+ * so a plan without that pilot is not a plan that can be completed.
110
+ */
111
+ missingPilot?: boolean;
112
+ /** Why the plan is incomplete, in plain words. */
113
+ planIncompleteReasons?: string[];
114
+ /** The workload of the job the plan describes. */
115
+ workload?: WorkloadAssessment;
104
116
  }
105
117
 
106
118
  /**
@@ -303,6 +315,35 @@ export function parseValidationPlan(item: TodoItem | null | undefined): Validati
303
315
  return plan;
304
316
  }
305
317
 
318
+ /**
319
+ * The workload of the job that a plan describes.
320
+ *
321
+ * The facts come from the plan itself: the dataset size of a tier is the
322
+ * size of the job, and the expected duration of a tier is the declared
323
+ * cost of it. A plan that declares a job of 10,000 records is a large
324
+ * job, whether or not it uses the words.
325
+ */
326
+ export function planWorkload(plan: ValidationPlan | null | undefined): WorkloadAssessment {
327
+ const tiers: WorkloadTierFacts[] = [];
328
+ for (const tier of [plan?.fast, plan?.full]) {
329
+ if (!tier) continue;
330
+ tiers.push({ datasetSize: tier.datasetSize, expectedDurationMs: tier.expectedDurationMs });
331
+ }
332
+ return assessWorkload({ tiers });
333
+ }
334
+
335
+ /**
336
+ * Does the plan declare a measured pilot for its scale-up?
337
+ *
338
+ * A pilot is the fast tier, and the scale-up is the final tier that names
339
+ * it with `scaleUpFrom: "fast"`. A fast tier alone is not the pilot of the
340
+ * final tier, because nothing then says that the final tier waits for it.
341
+ */
342
+ export function planHasPilot(plan: ValidationPlan | null | undefined): boolean {
343
+ if (!plan?.fast) return false;
344
+ return plan.full?.scaleUpFrom === "fast";
345
+ }
346
+
306
347
  /**
307
348
  * Describe one tier in a single line: command, expected cost, dataset size.
308
349
  */
@@ -389,7 +430,21 @@ export function summarizeValidationState(
389
430
  };
390
431
 
391
432
  const finalTierRequired = plan.finalTierRequired === true;
392
- const planIncomplete = finalTierRequired && plan.full === undefined;
433
+ const workload = planWorkload(plan);
434
+ const missingPilot = finalTierRequired && workload.isLarge && !planHasPilot(plan);
435
+ const planIncompleteReasons: string[] = [];
436
+ if (finalTierRequired && plan.full === undefined) {
437
+ planIncompleteReasons.push("the plan requires a final tier but declares none");
438
+ }
439
+ if (missingPilot) {
440
+ const size = workload.recordCount !== undefined ? `${workload.recordCount} record(s)` : "a large scope";
441
+ planIncompleteReasons.push(
442
+ plan.fast
443
+ ? `the plan declares a large job (${size}) but the final tier does not name the pilot as its scale-up`
444
+ : `the plan declares a large job (${size}) but declares no pilot tier`,
445
+ );
446
+ }
447
+ const planIncomplete = (finalTierRequired && plan.full === undefined) || missingPilot;
393
448
  const fast = stateOf("fast");
394
449
  let full = stateOf("full");
395
450
  // A scale-up that waits for its pilot only counts when the pilot
@@ -406,7 +461,10 @@ export function summarizeValidationState(
406
461
  finalTierRequired,
407
462
  finalTierPending: finalTierRequired && plan.full !== undefined && full.status !== "passed",
408
463
  planIncomplete,
464
+ missingPilot,
465
+ workload,
409
466
  };
467
+ if (planIncompleteReasons.length > 0) state.planIncompleteReasons = planIncompleteReasons;
410
468
  if (plan.budgetMinutes !== undefined) state.budgetMinutes = plan.budgetMinutes;
411
469
  return state;
412
470
  }
@@ -490,9 +548,19 @@ export function formatValidationContext(
490
548
  );
491
549
  }
492
550
  if (state.planIncomplete) {
493
- lines.push(
494
- "The plan requires a final tier but declares none: report that instead of completing the todo silently.",
495
- );
551
+ const reasons = state.planIncompleteReasons && state.planIncompleteReasons.length > 0
552
+ ? state.planIncompleteReasons.join("; ")
553
+ : "the plan requires a final tier but declares none";
554
+ lines.push(`The plan cannot be completed as it is: ${reasons}.`);
555
+ if (state.missingPilot) {
556
+ lines.push(
557
+ "A large job is accepted by the scale-up of a representative pilot: declare a fast tier with " +
558
+ '"representative": true and a final tier with "scaleUpFrom": "fast", then record the pilot.',
559
+ );
560
+ }
561
+ }
562
+ if (state.workload && state.workload.size !== "unknown") {
563
+ lines.push(`Workload: ${describeWorkload(state.workload)}.`);
496
564
  }
497
565
  }
498
566