@tea-agent/loop-agent 0.42.0-next.6 → 0.42.0-next.8
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/AGENTS.md +1 -1
- package/CHANGELOG.md +20 -0
- package/dist/build-stamp.json +3 -3
- package/dist/executors/dag-pi-executor.js +74 -13
- package/dist/executors/shell-executor.js +3 -1
- package/dist/task/source-prepare/fragment-inventory.js +49 -16
- package/dist/task/source-prepare/prepare.js +37 -20
- package/dist/worker/console/chat/model-resolver.js +7 -4
- package/dist/worker/console/chat/pi-runtime.js +60 -5
- package/dist/worker/console/chat/routes.js +69 -46
- package/dist/worker/console/chat/sdd-data-alignment.js +6 -3
- package/dist/worker/console/chat/session-store.js +16 -0
- package/dist/worker/console/pi-readiness.js +3 -2
- package/dist/worker/console/server.js +19 -1
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-BJpsGgAv.js → abnfDiagram-N423BO3Z-C6n9_m0P.js} +1 -1
- package/dist/worker/console/static/assets/{arc-DjMFPsEd.js → arc-DQh-IfZ1.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-DqdZlr1O.js → architectureDiagram-T3A2C74G-54NnrwUC.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-BmZnfFEO.js → blockDiagram-VBNYF7ZC-pivRALGK.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-3cKberpL.js → c4Diagram-5PPSVZJV-BR7OV2NJ.js} +1 -1
- package/dist/worker/console/static/assets/channel-DsZxrgqe.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-q-0yjQ1h.js → chunk-2GRJ4B5K-B1Aq6BcK.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-DnONBaDW.js → chunk-2Q5K7J3B-CbdU0rjo.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-v3a6PCOS.js → chunk-5RXB4S5H-Dqi2GJeD.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-D1kLrlU_.js → chunk-5VM5RSS4-BYn1Hu4R.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-BPelzihi.js → chunk-6Q2QTUOP-DRLjDL0k.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-DauNE69H.js → chunk-GF5L2VYU-CY9Xx2jV.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-RIg5_bFJ.js → chunk-JWPE2WC7-BNCZs7_z.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-BuILuoxs.js → chunk-KBJHAD2P-DZ8AStLL.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-VHORV5oW.js → chunk-RYQCIY6F-DsxjYrzz.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-BJYr4xHC.js → chunk-XXDRQBXY-Ddx1KC1l.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-CuToPeGV.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-CuToPeGV.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BS6nuqQn.js → cose-bilkent-JH36ORCC-W9TveCnK.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BBhF0NAT.js → cynefin-VYW2F7L2-l9j_ztZH.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-D1135Ifg.js → cynefinDiagram-MW4NZA55-BMJMKi4G.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-vAOYPUWJ.js → dagre-VZM6K2ZE-Bb4mG9pH.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-DuaSPkfa.js → diagram-7IWD3JNH-BMgL_Qv0.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-mMvXvGU6.js → diagram-B4RE2ZJO-Bvo2T4OQ.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-CDIEJtlL.js → diagram-LBJQPF4R-_5kWRN9c.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-Bi_6JvCW.js → diagram-Q27KOJAE-DqdMrltM.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-DAqj57yN.js → diagram-UB23O5K3-CDDYsNkp.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-DfWaxNEo.js → ebnfDiagram-BXEA7PRR-c4nfnZUV.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-Bt7QqkNG.js → erDiagram-JOGREHBK-DnkUcNMs.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CRRhQwT2.js → flowDiagram-UKHOOZJN-Dz0KGLZE.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-DAV1O5B6.js → ganttDiagram-PKOTCBZU-Cj1t1uka.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-0X2Qo4Lp.js → gitGraphDiagram-DS77QQ5N-tp2FrBHd.js} +1 -1
- package/dist/worker/console/static/assets/index-D9Sc0f0y.js +449 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-C5W5eILC.js → infoDiagram-6WML65LV-DQwJS8DH.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-5SfqU-Tb.js → ishikawaDiagram-WSZJBQD7-eimCQWD4.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-CKDRcaKf.js → journeyDiagram-NVQOT4AX-Dd9Gbwgv.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-Cc7_TuSb.js → kanban-definition-27J2QSJJ-Qve3cyft.js} +1 -1
- package/dist/worker/console/static/assets/{linear-BcQYYmeT.js → linear-UXKSe36Z.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-CwLZPtQo.js → mermaid.core-CWhj4JXN.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-D29M025z.js → mindmap-definition-FAOFIHXS-yTcwf4SJ.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-CV-ZAWYe.js → pegDiagram-VL7TDLO6-_7qToaZP.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-C6iPS_o5.js → pieDiagram-7S7Q4E2Y-CXmrKmDm.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-BFCD1sTg.js → quadrantDiagram-CIZ2JOQS-hMmrJyaS.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-CztNj4O9.js → railroadDiagram-AXF67PYL-BXw8tCBe.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-BWIELSNd.js → requirementDiagram-LRYGKXZP-BmjgnLZl.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-CPtxrjJQ.js → sankeyDiagram-W5VNT64P-Bj77SPpN.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-BEWVSFsu.js → sequenceDiagram-SI44F4Z6-B2FDVysL.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-DyhzoX_J.js → sizeCapture-X5ZJPWSS-UChNY9ZZ.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-xLox0nu7.js → stateDiagram-OKZ733FA-wbAEkbd7.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-yPoT19ft.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-CLC_ow9A.js → swimlanes-SLNWSIFB-DCEkU8HR.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-VWdarchG.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-DmJZ4uyY.js → timeline-definition-Z64GVDOM-BGp7H06U.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-DD7GGi9Z.js → vennDiagram-T6HMQDX7-DsCIQuWR.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-Bi8PQoGt.js → wardleyDiagram-T6FBY63Y-cRZLP4Xe.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-uKdAY8Dp.js → xychartDiagram-ELKLHX3M-CP0xLR9Y.js} +1 -1
- package/dist/worker/console/static/index.html +1 -1
- package/dist/worker/console/static-src/app/useRecoveryConsole.js +5 -5
- package/dist/worker/console/static-src/operator-chat/input-history.js +8 -6
- package/dist/worker/console/static-src/operator-chat/runtime-snapshot-store.js +10 -0
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +31 -2
- package/dist/worker/console/static-src/operator-chat/useComposer.js +8 -3
- package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +29 -6
- package/dist/worker/console/static-src/operator-chat/useRuntimeSnapshot.js +8 -2
- package/dist/worker/console/static-src/shell/console-update-reload.js +36 -9
- package/dist/worker/console/static-src/shell/workspace-route.js +11 -0
- package/dist/worker/observe/static/dag-helpers.js +4 -2
- package/dist/worker/observe/static/inspect-workspace.js +23 -0
- package/dist/worker/observe/static/kpi.js +2 -2
- package/dist/worker/observe/static/operator-chrome.d.ts +10 -2
- package/dist/worker/observe/static/operator-chrome.js +37 -23
- package/dist/worker/observe/static/relations.js +2 -2
- package/dist/worker/observe/static/router.d.ts +12 -1
- package/dist/worker/observe/static/router.js +48 -6
- package/dist/worker/observe/static/run-processing.js +4 -2
- package/dist/worker/observe/static/shell-chrome.js +2 -2
- package/dist/worker/observe/static/state.js +5 -0
- package/dist/worker/observe/static/styles.css +95 -0
- package/dist/worker/observe/static/views/dag-inspector.js +14 -7
- package/dist/worker/observe/static/views/dag.js +10 -2
- package/dist/worker/observe/static/views/dags.js +3 -1
- package/dist/worker/observe/static/views/dashboard.js +14 -6
- package/dist/worker/observe/static/views/pool.js +7 -2
- package/dist/worker/observe/static/views/run.js +12 -3
- package/dist/worker/observe/static/views/session-timeline.js +69 -10
- package/dist/worker/observe/static/views/task.js +7 -2
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +40 -9
- package/dist/workflows/dag/backend-test-scenario-param.js +126 -19
- package/dist/workflows/dag/backend-test-scenario-partitions.js +62 -1
- package/dist/workflows/dag/backend-test-writer-completeness.js +7 -3
- package/dist/workflows/dag/frontend-shadow-dual-write.js +33 -6
- package/dist/workflows/dag/init-hybrid.js +175 -27
- package/dist/workflows/dag/node-execution.js +1 -1
- package/dist/workflows/dag/types.js +1 -1
- package/package.json +3 -2
- package/dist/worker/console/static/assets/channel-DWYwciiv.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-D6jOf_l4.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-D6jOf_l4.js +0 -1
- package/dist/worker/console/static/assets/index-CVdddQsA.js +0 -451
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DS5ZRvJG.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Dvpxn-bf.js +0 -8
|
@@ -59,6 +59,7 @@ const CLOSED_SET = new Set([
|
|
|
59
59
|
"null",
|
|
60
60
|
"min-1",
|
|
61
61
|
"min",
|
|
62
|
+
"max-1",
|
|
62
63
|
"max",
|
|
63
64
|
"max+1",
|
|
64
65
|
"pattern-invalid",
|
|
@@ -155,14 +156,19 @@ function parseScenarioBoundToken(raw) {
|
|
|
155
156
|
function parseScenarioIntentLine(line) {
|
|
156
157
|
// Target may contain commas (body.title,body.status). Intent may contain `|` / spaces.
|
|
157
158
|
// Stop field values only at known trailing keys or end-of-line.
|
|
158
|
-
const
|
|
159
|
+
const unfenced = line
|
|
160
|
+
.trim()
|
|
161
|
+
.replace(/^[-*+]\s+/, "")
|
|
162
|
+
.replace(/^`(.+)`$/, "$1")
|
|
163
|
+
.trim();
|
|
164
|
+
const precise = /场景意图\s*[::]\s*(TP-[A-Z0-9-]+)\s*[|;]\s*operation\s*=\s*([^;\n]+?)\s*[|;]\s*target\s*=\s*([^;\n]+?)\s*[|;]\s*intent\s*=\s*([^;\n]+?)(?:\s*[|;]\s*bound\s*=\s*([^\s;`|\n]+))?(?:\s*[|;]\s*example\s*=\s*([^;\n]*))?(?:\s*[|;]\s*expectedCode\s*=\s*[^;\n]+)?\s*$/i.exec(unfenced);
|
|
159
165
|
if (precise) {
|
|
160
166
|
return {
|
|
161
167
|
tpId: precise[1],
|
|
162
168
|
rawIntent: (precise[4] ?? "unknown").trim(),
|
|
163
169
|
target: precise[3]?.trim(),
|
|
164
170
|
bound: parseScenarioBoundToken(precise[5]),
|
|
165
|
-
example: precise[6]
|
|
171
|
+
example: precise[6] === undefined ? undefined : precise[6].trim(),
|
|
166
172
|
legacy: false,
|
|
167
173
|
};
|
|
168
174
|
}
|
|
@@ -192,6 +198,53 @@ function resolveScenarioIntentField(target, field) {
|
|
|
192
198
|
target.split(/\s*,\s*/)[0];
|
|
193
199
|
return preferred?.split(".").at(-1);
|
|
194
200
|
}
|
|
201
|
+
function normalizeLengthIntentAlias(rawIntent) {
|
|
202
|
+
const lower = rawIntent.trim().toLowerCase();
|
|
203
|
+
if (lower === "max-1" || lower === "maxminus1" || lower === "max_minus_1") {
|
|
204
|
+
return "max-1";
|
|
205
|
+
}
|
|
206
|
+
if (lower === "min-1" || lower === "minminus1" || lower === "min_minus_1") {
|
|
207
|
+
return "min-1";
|
|
208
|
+
}
|
|
209
|
+
return lower;
|
|
210
|
+
}
|
|
211
|
+
function proseLengthBound(haystack, field) {
|
|
212
|
+
const fieldAlt = field ? field.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") : undefined;
|
|
213
|
+
const scoped = fieldAlt ? new RegExp(`${fieldAlt}[^\\n]{0,160}`, "ig") : undefined;
|
|
214
|
+
const windows = [];
|
|
215
|
+
if (scoped) {
|
|
216
|
+
for (const match of haystack.matchAll(scoped)) {
|
|
217
|
+
windows.push(match[0] ?? "");
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
windows.push(haystack);
|
|
221
|
+
let max;
|
|
222
|
+
let min;
|
|
223
|
+
const proseMaxRe = /(\d{1,4})\s*个字符的最大/i;
|
|
224
|
+
for (const window of windows) {
|
|
225
|
+
const proseMax = proseMaxRe.exec(window);
|
|
226
|
+
if (proseMax && max === undefined)
|
|
227
|
+
max = Number(proseMax[1]);
|
|
228
|
+
const nums = [...window.matchAll(/(?<![+\-])\d{1,4}/g)]
|
|
229
|
+
.map((item) => Number(item[0]))
|
|
230
|
+
.filter((item) => Number.isFinite(item));
|
|
231
|
+
for (let i = 0; i + 2 < nums.length; i += 1) {
|
|
232
|
+
const a = nums[i];
|
|
233
|
+
const b = nums[i + 1];
|
|
234
|
+
const c = nums[i + 2];
|
|
235
|
+
// Prefer n-1,n,n+1 around a documented max (100/2000), not 0,1,99 or 99,100,101's first two plus leftover.
|
|
236
|
+
if (b === a + 1 && c === b + 1 && b >= 10) {
|
|
237
|
+
max ??= b;
|
|
238
|
+
break;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
if (nums.includes(0) && nums.includes(1))
|
|
242
|
+
min ??= 1;
|
|
243
|
+
if (max !== undefined && min !== undefined)
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
return { max, min };
|
|
247
|
+
}
|
|
195
248
|
function resolveCustomLiteralIntent(rawIntent, field) {
|
|
196
249
|
const lower = rawIntent.toLowerCase();
|
|
197
250
|
if (!lower.startsWith("custom-literal:")) {
|
|
@@ -250,7 +303,7 @@ export function inferScenarioParamIntent(input) {
|
|
|
250
303
|
const field = resolveScenarioIntentField(machine.target, machine.field);
|
|
251
304
|
const rawIntent = machine.rawIntent.trim();
|
|
252
305
|
const example = machine.example;
|
|
253
|
-
const lower = rawIntent
|
|
306
|
+
const lower = normalizeLengthIntentAlias(rawIntent);
|
|
254
307
|
let normalizedIntent = lower === "nominal-operation" ? "nominal" : lower;
|
|
255
308
|
// Writers sometimes mark a short "typical" length sample as min/max with bound=LEN<nominal>.
|
|
256
309
|
// Treat that as nominal so a short helper/string is not forced to equal maxLength.
|
|
@@ -273,16 +326,16 @@ export function inferScenarioParamIntent(input) {
|
|
|
273
326
|
new RegExp(`(?:minLength|min(?:imum)?|下限|最小长度)\\s*[=::]?\\s*(\\d{1,4})[^\\n]{0,80}?${fieldEsc}`, "i").exec(haystack) ??
|
|
274
327
|
/(?:minLength|min(?:imum)?|下限|最小长度)\s*[=::]?\s*(\d{1,4})/i.exec(haystack)
|
|
275
328
|
: /(?:minLength|min(?:imum)?|下限|最小长度)\s*[=::]?\s*(\d{1,4})/i.exec(haystack);
|
|
329
|
+
const proseBound = proseLengthBound(haystack, field);
|
|
276
330
|
const prefersMinBound = normalizedIntent === "min" ||
|
|
277
331
|
normalizedIntent === "min-1" ||
|
|
278
332
|
/(?:MIN|LEN1|LENGTH-?1|最短|最小)/i.test(tpId);
|
|
279
|
-
const boundFromBody = prefersMinBound
|
|
280
|
-
? minBoundFromBody ?? maxBoundFromBody
|
|
281
|
-
: maxBoundFromBody ?? minBoundFromBody;
|
|
333
|
+
const boundFromBody = prefersMinBound ? minBoundFromBody : maxBoundFromBody ?? minBoundFromBody;
|
|
282
334
|
const boundFromTp = /(?:LEN(?:GTH)?|长度)[_-]?(\d{1,5})(?:[_-]|$)/i.exec(tpId) ??
|
|
283
|
-
/(?:MIN|MAX)[_-]
|
|
335
|
+
/(?:MIN|MAX)[_-](\d{2,5})(?:[_-]|$)/i.exec(tpId);
|
|
284
336
|
const lengthIntent = normalizedIntent === "min" ||
|
|
285
337
|
normalizedIntent === "min-1" ||
|
|
338
|
+
normalizedIntent === "max-1" ||
|
|
286
339
|
normalizedIntent === "max" ||
|
|
287
340
|
normalizedIntent === "max+1";
|
|
288
341
|
// Length intents: explicit machine bound >0, then TP-encoded LEN1/LEN100,
|
|
@@ -294,13 +347,35 @@ export function inferScenarioParamIntent(input) {
|
|
|
294
347
|
: normalizedIntent === "max+1"
|
|
295
348
|
? Math.max(0, boundFromTpNumber - 1)
|
|
296
349
|
: boundFromTpNumber;
|
|
297
|
-
const
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
350
|
+
const boundFromProse = prefersMinBound ? proseBound.min : proseBound.max;
|
|
351
|
+
const offsetIntent = normalizedIntent === "max-1" ||
|
|
352
|
+
normalizedIntent === "max+1" ||
|
|
353
|
+
normalizedIntent === "min-1";
|
|
354
|
+
const contractBound = boundFromBody
|
|
355
|
+
? Number(boundFromBody[1])
|
|
356
|
+
: boundFromProse !== undefined && boundFromProse >= 0
|
|
357
|
+
? boundFromProse
|
|
358
|
+
: undefined;
|
|
359
|
+
const machineBoundPositive = typeof machine.bound === "number" && Number.isFinite(machine.bound) && machine.bound > 0;
|
|
360
|
+
const minLengthFromBody = prefersMinBound && minBoundFromBody ? Number(minBoundFromBody[1]) : undefined;
|
|
361
|
+
const machineBoundZeroMin = prefersMinBound &&
|
|
362
|
+
machine.bound === 0 &&
|
|
363
|
+
minLengthFromBody === undefined &&
|
|
364
|
+
boundFromTpNormalized === undefined;
|
|
365
|
+
const bound = offsetIntent && contractBound !== undefined && contractBound > 0
|
|
366
|
+
? contractBound
|
|
367
|
+
: machineBoundPositive && !offsetIntent
|
|
368
|
+
? machine.bound
|
|
369
|
+
: lengthIntent && boundFromTpNormalized !== undefined
|
|
370
|
+
? boundFromTpNormalized
|
|
371
|
+
: minLengthFromBody !== undefined && minLengthFromBody >= 0
|
|
372
|
+
? minLengthFromBody
|
|
373
|
+
: machineBoundZeroMin
|
|
374
|
+
? 0
|
|
375
|
+
: contractBound !== undefined &&
|
|
376
|
+
(prefersMinBound ? contractBound >= 0 : contractBound > 0)
|
|
377
|
+
? contractBound
|
|
378
|
+
: undefined;
|
|
304
379
|
if (rawIntent.toLowerCase().startsWith("custom-literal:")) {
|
|
305
380
|
const resolved = resolveCustomLiteralIntent(rawIntent, field);
|
|
306
381
|
const rawLiteral = rawIntent.slice(rawIntent.indexOf(":") + 1).trim();
|
|
@@ -387,13 +462,15 @@ export function inferScenarioParamIntent(input) {
|
|
|
387
462
|
const boundMatch = fieldScopedBound ??
|
|
388
463
|
/(?:maxLength|max(?:imum)?|上限|最大长度)\s*[=::]?\s*(\d{1,4})/i.exec(`${caseBody}\n${dataSection}`) ??
|
|
389
464
|
/(?:minLength|min(?:imum)?|下限|最小长度)\s*[=::]?\s*(\d{1,4})/i.exec(`${caseBody}\n${dataSection}`);
|
|
390
|
-
const
|
|
465
|
+
const proseBound = proseLengthBound(`${caseBody}
|
|
466
|
+
${dataSection}`, field);
|
|
467
|
+
const bound = boundMatch ? Number(boundMatch[1]) : proseBound.max ?? proseBound.min;
|
|
391
468
|
const exampleMatch = /(?:反例|invalid example|example)\s*[=::]\s*[`"]?([^`"\n]+)[`"]?/i.exec(caseBody);
|
|
392
469
|
const example = exampleMatch?.[1]?.trim();
|
|
393
470
|
if (/EMPTY|空白|空串|空字符串/.test(upper) || /空字符串|空串|empty string/i.test(caseBody)) {
|
|
394
471
|
return { intent: "empty", field, bound, example, intentSource: "tp-fallback" };
|
|
395
472
|
}
|
|
396
|
-
if (/MISSING|缺省|缺失|省略/.test(upper) || /缺少字段|缺失字段|missing field/i.test(caseBody)) {
|
|
473
|
+
if (/MISSING|OMITTED|\bOMIT\b|缺省|缺失|省略/.test(upper) || /缺少字段|缺失字段|missing field/i.test(caseBody)) {
|
|
397
474
|
return { intent: "missing", field, bound, example, intentSource: "tp-fallback" };
|
|
398
475
|
}
|
|
399
476
|
if (/\bNULL\b|空值/.test(upper) || /\bnull\b/i.test(caseBody)) {
|
|
@@ -402,6 +479,9 @@ export function inferScenarioParamIntent(input) {
|
|
|
402
479
|
if (/MAX[-_]?PLUS[-_]?1|MAX\+1|超长|越界|LENGTH[-_]?OVER|OVER[-_]?LENGTH|OVER/.test(upper)) {
|
|
403
480
|
return { intent: "max+1", field, bound, example, intentSource: "tp-fallback" };
|
|
404
481
|
}
|
|
482
|
+
if (/MAX[-_]1(?:$|[-_])|max-1/i.test(upper)) {
|
|
483
|
+
return { intent: "max-1", field, bound, example, intentSource: "tp-fallback" };
|
|
484
|
+
}
|
|
405
485
|
if (/\bMAX\b|最大/.test(upper)) {
|
|
406
486
|
return { intent: "max", field, bound, example, intentSource: "tp-fallback" };
|
|
407
487
|
}
|
|
@@ -425,7 +505,7 @@ export function inferScenarioParamIntent(input) {
|
|
|
425
505
|
}
|
|
426
506
|
return { intent: "unknown", field, bound, example, intentSource: "tp-fallback" };
|
|
427
507
|
}
|
|
428
|
-
const OMIT_SENTINEL_RE = /^(?:_OMIT|OMIT|MISSING|Ellipsis|\.\.\.)\b/;
|
|
508
|
+
const OMIT_SENTINEL_RE = /^(?:_OMIT|_MISSING|OMIT|MISSING|Ellipsis|\.\.\.)\b/;
|
|
429
509
|
const HTTP_EXPECTED_CODE_RE = /^(?:2xx|3xx|4xx|5xx|[1-5]\d{2}|HTTP_[1-5]\d{2})$/i;
|
|
430
510
|
/** Semantic custom-literal tokens that describe a value class, not a raw string. */
|
|
431
511
|
function isWhitespaceSemanticLiteral(token) {
|
|
@@ -774,6 +854,10 @@ export function observeParamFeatures(block, field, constants, parameterNames) {
|
|
|
774
854
|
(/["'][A-Za-z_][A-Za-z0-9_]*["']\s*:/.test(dictText) ||
|
|
775
855
|
/(?:payload|body|data|json)\s*=/.test(block));
|
|
776
856
|
if (dictLooksLikePayload && !field) {
|
|
857
|
+
const emptyField = /["']([A-Za-z_][A-Za-z0-9_.]*)["']\s*:\s*(?:""|'')/.exec(dictText);
|
|
858
|
+
if (emptyField?.[1]) {
|
|
859
|
+
return { kind: "empty-string", text: '""', length: 0, literal: "" };
|
|
860
|
+
}
|
|
777
861
|
return { kind: "dict", text: dictText.replace(/\s+/g, " ").slice(0, 160) };
|
|
778
862
|
}
|
|
779
863
|
if (dictLooksLikePayload && field) {
|
|
@@ -803,7 +887,7 @@ export function observeParamFeatures(block, field, constants, parameterNames) {
|
|
|
803
887
|
};
|
|
804
888
|
}
|
|
805
889
|
// Bare omit sentinel before id=...
|
|
806
|
-
if (/pytest\.param\s*\(\s*(?:_OMIT|OMIT|MISSING|\.\.\.|Ellipsis)\s*(?:,|\)|$)/.test(block)) {
|
|
890
|
+
if (/pytest\.param\s*\(\s*(?:_OMIT|_MISSING|OMIT|MISSING|\.\.\.|Ellipsis)\s*(?:,|\)|$)/.test(block)) {
|
|
807
891
|
return {
|
|
808
892
|
kind: "missing-key",
|
|
809
893
|
text: field ? `missing:${field}` : "missing-key",
|
|
@@ -865,7 +949,7 @@ export function observeParamFeatures(block, field, constants, parameterNames) {
|
|
|
865
949
|
if (paramOpen) {
|
|
866
950
|
const afterOpen = block.slice((paramOpen.index ?? 0) + paramOpen[0].length);
|
|
867
951
|
const positionalTokens = [];
|
|
868
|
-
const tokenRe = /\s*(_OMIT|OMIT|MISSING|Ellipsis|\.\.\.|None|True|False|-?\d+(?:\.\d+)?|"""[\s\S]*?"""|\'\'\'[\s\S]*?\'\'\'|(?:["'][^"'\\]["']\s*\*\s*(?:\d{1,5}|[A-Za-z_][A-Za-z0-9_]*))|"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|[A-Za-z_][A-Za-z0-9_]*)\s*(?:,|\)|$)/g;
|
|
952
|
+
const tokenRe = /\s*(_OMIT|_MISSING|OMIT|MISSING|Ellipsis|\.\.\.|None|True|False|-?\d+(?:\.\d+)?|"""[\s\S]*?"""|\'\'\'[\s\S]*?\'\'\'|(?:["'][^"'\\]["']\s*\*\s*(?:\d{1,5}|[A-Za-z_][A-Za-z0-9_]*))|"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|[A-Za-z_][A-Za-z0-9_]*)\s*(?:,|\)|$)/g;
|
|
869
953
|
let match;
|
|
870
954
|
while ((match = tokenRe.exec(afterOpen)) !== null) {
|
|
871
955
|
const between = afterOpen.slice(0, match.index);
|
|
@@ -1008,12 +1092,25 @@ function compareIntent(intent, observed, bound, example, field) {
|
|
|
1008
1092
|
return "MATCH";
|
|
1009
1093
|
return len !== undefined ? "MISMATCH" : "UNDETERMINED";
|
|
1010
1094
|
}
|
|
1095
|
+
case "max-1":
|
|
1096
|
+
if (bound === undefined)
|
|
1097
|
+
return "UNDETERMINED";
|
|
1098
|
+
{
|
|
1099
|
+
const expectedMax1 = Math.max(0, bound - 1);
|
|
1100
|
+
const len = observedLengthValue(observed);
|
|
1101
|
+
if (len === expectedMax1)
|
|
1102
|
+
return "MATCH";
|
|
1103
|
+
return len !== undefined ? "MISMATCH" : "UNDETERMINED";
|
|
1104
|
+
}
|
|
1011
1105
|
case "min":
|
|
1012
1106
|
if (bound === undefined)
|
|
1013
1107
|
return "UNDETERMINED";
|
|
1014
1108
|
{
|
|
1015
1109
|
if (isMinLengthHelper(observed))
|
|
1016
1110
|
return "MATCH";
|
|
1111
|
+
// Optional min=0 may omit the key instead of sending "".
|
|
1112
|
+
if (bound === 0 && observed.kind === "missing-key")
|
|
1113
|
+
return "MATCH";
|
|
1017
1114
|
const len = observedLengthValue(observed);
|
|
1018
1115
|
if (len === bound)
|
|
1019
1116
|
return "MATCH";
|
|
@@ -1167,6 +1264,10 @@ function suggestedFixFor(intent, field, bound, example) {
|
|
|
1167
1264
|
return field && bound !== undefined
|
|
1168
1265
|
? `set ${field}="x"*${bound + 1}`
|
|
1169
1266
|
: undefined;
|
|
1267
|
+
case "max-1":
|
|
1268
|
+
return field && bound !== undefined
|
|
1269
|
+
? `set ${field}="x"*${Math.max(0, bound - 1)}`
|
|
1270
|
+
: undefined;
|
|
1170
1271
|
case "min":
|
|
1171
1272
|
return field && bound !== undefined
|
|
1172
1273
|
? `set ${field}="x"*${bound}`
|
|
@@ -1720,6 +1821,12 @@ export function deterministicRewriteScenarioParam(input) {
|
|
|
1720
1821
|
}
|
|
1721
1822
|
valueLiteral = `"x" * ${bound + 1}`;
|
|
1722
1823
|
break;
|
|
1824
|
+
case "max-1":
|
|
1825
|
+
if (bound === undefined) {
|
|
1826
|
+
return { ok: false, source: input.source, detail: "missing bound for max-1" };
|
|
1827
|
+
}
|
|
1828
|
+
valueLiteral = `"x" * ${Math.max(0, bound - 1)}`;
|
|
1829
|
+
break;
|
|
1723
1830
|
case "min-1":
|
|
1724
1831
|
if (bound === undefined) {
|
|
1725
1832
|
return { ok: false, source: input.source, detail: "missing bound for min-1" };
|
|
@@ -43,6 +43,67 @@ function splitCells(raw) {
|
|
|
43
43
|
.map((cell) => cell.trim())
|
|
44
44
|
.filter((cell, index, cells) => !(cell === "" && (index === 0 || index === cells.length - 1)));
|
|
45
45
|
}
|
|
46
|
+
function isExactSlotInventory(extras, partitionId) {
|
|
47
|
+
const tokens = extras
|
|
48
|
+
.join(";")
|
|
49
|
+
.split(/[;,,;]/)
|
|
50
|
+
.map((token) => token.trim().replace(/`/g, ""))
|
|
51
|
+
.filter((token) => token.length > 0);
|
|
52
|
+
if (tokens.length === 0)
|
|
53
|
+
return false;
|
|
54
|
+
const prefix = `TP-${partitionId.trim().toUpperCase()}-`;
|
|
55
|
+
return tokens.every((token) => /^TP-[A-Z0-9-]+$/i.test(token) &&
|
|
56
|
+
(token.toUpperCase().startsWith(prefix) || token.toUpperCase().startsWith("TP-SP-")));
|
|
57
|
+
}
|
|
58
|
+
function trimExactSlotInventoryCells(cells) {
|
|
59
|
+
if (cells.length <= 7)
|
|
60
|
+
return { cells, trimmed: false };
|
|
61
|
+
const partitionId = cells[0] ?? "";
|
|
62
|
+
if (!isExactSlotInventory(cells.slice(7), partitionId)) {
|
|
63
|
+
return { cells, trimmed: false };
|
|
64
|
+
}
|
|
65
|
+
return { cells: cells.slice(0, 7), trimmed: true };
|
|
66
|
+
}
|
|
67
|
+
export function canonicalizeScenarioPartitionTable(markdown) {
|
|
68
|
+
const lines = markdown.replaceAll("\r\n", "\n").replaceAll("\r", "\n").split("\n");
|
|
69
|
+
const headingIndex = lines.findIndex((line) => PARTITION_HEADING.test(line.trim()));
|
|
70
|
+
if (headingIndex === -1) {
|
|
71
|
+
return { markdown: lines.join("\n"), changed: false, trimmedRows: 0 };
|
|
72
|
+
}
|
|
73
|
+
let headerIndex = -1;
|
|
74
|
+
let sectionEnd = lines.length;
|
|
75
|
+
for (let i = headingIndex + 1; i < lines.length; i++) {
|
|
76
|
+
if (/^##\s+/.test(lines[i].trim()) && !PARTITION_HEADING.test(lines[i].trim())) {
|
|
77
|
+
sectionEnd = i;
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
if (PARTITION_TABLE_HEADER.test(lines[i])) {
|
|
81
|
+
headerIndex = i;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (headerIndex === -1) {
|
|
85
|
+
return { markdown: lines.join("\n"), changed: false, trimmedRows: 0 };
|
|
86
|
+
}
|
|
87
|
+
let trimmedRows = 0;
|
|
88
|
+
for (let i = headerIndex + 2; i < sectionEnd; i++) {
|
|
89
|
+
const line = lines[i];
|
|
90
|
+
if (!TABLE_ROW.test(line)) {
|
|
91
|
+
if (/^##\s+/.test(line) || /^#\s+/.test(line))
|
|
92
|
+
break;
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
const { cells, trimmed } = trimExactSlotInventoryCells(splitCells(line));
|
|
96
|
+
if (!trimmed)
|
|
97
|
+
continue;
|
|
98
|
+
lines[i] = `| ${cells.join(" | ")} |`;
|
|
99
|
+
trimmedRows += 1;
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
markdown: lines.join("\n"),
|
|
103
|
+
changed: trimmedRows > 0,
|
|
104
|
+
trimmedRows,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
46
107
|
/**
|
|
47
108
|
* Parse the `## Scenario Partitions` section of the README into declared
|
|
48
109
|
* partitions. Returns { partitions, issues }; issues are fail-closed parse
|
|
@@ -77,7 +138,7 @@ export function parseScenarioPartitions(readmeMarkdown) {
|
|
|
77
138
|
break;
|
|
78
139
|
continue;
|
|
79
140
|
}
|
|
80
|
-
const cells = splitCells(line);
|
|
141
|
+
const { cells } = trimExactSlotInventoryCells(splitCells(line));
|
|
81
142
|
if (cells.length !== 7) {
|
|
82
143
|
issues.push(`MISSING_PARTITION: row has ${cells.length} cells, expected 7: ${line.trim()}`);
|
|
83
144
|
continue;
|
|
@@ -369,8 +369,6 @@ function pythonParseable(source) {
|
|
|
369
369
|
const normalized = source.replace(/\r\n/g, "\n");
|
|
370
370
|
if (!normalized.trim())
|
|
371
371
|
return false;
|
|
372
|
-
if (/^\s*(?:def|class|import|from)\b/m.test(normalized) === false)
|
|
373
|
-
return false;
|
|
374
372
|
const structural = pythonStructureOutsideStrings(normalized);
|
|
375
373
|
if (!structural.stringsClosed)
|
|
376
374
|
return false;
|
|
@@ -387,7 +385,13 @@ function pythonParseable(source) {
|
|
|
387
385
|
return false;
|
|
388
386
|
if (openBraces !== closeBraces)
|
|
389
387
|
return false;
|
|
390
|
-
|
|
388
|
+
// Business modules still need a statement. Evidence-only mapped scripts may
|
|
389
|
+
// be a closed module docstring + Case-ID with no `def test_` (R10 health).
|
|
390
|
+
if (/^\s*(?:def|class|import|from|__all__\s*=)/m.test(normalized))
|
|
391
|
+
return true;
|
|
392
|
+
return (/^\s*(?:'''|""")/.test(normalized) &&
|
|
393
|
+
/\bCase-ID\s*:/i.test(normalized) &&
|
|
394
|
+
!/^\s*(?:async\s+)?def\s+test_/m.test(normalized));
|
|
391
395
|
}
|
|
392
396
|
/**
|
|
393
397
|
* Forbidden generated shared helper/factory namespaces for the self-contained
|
|
@@ -659,10 +659,13 @@ function latestCommittedScoutTargetSurface(records) {
|
|
|
659
659
|
}
|
|
660
660
|
/**
|
|
661
661
|
* Scout owns discovery. A complete fact must explicitly say so, name at least
|
|
662
|
-
* one target candidate
|
|
663
|
-
* evidence for a named target
|
|
664
|
-
*
|
|
665
|
-
*
|
|
662
|
+
* one target candidate and leave no unresolved paths. Existing surfaces must
|
|
663
|
+
* include fresh runtime evidence for a named target path. Greenfield
|
|
664
|
+
* surfaces are the one explicit exception: when runtime-injected source
|
|
665
|
+
* declarations prove that every named path was specified by the task source,
|
|
666
|
+
* all paths may be future (not yet on disk). This keeps create-new work
|
|
667
|
+
* possible without allowing Scout to invent an arbitrary path under the
|
|
668
|
+
* writer boundary. An empty test path list remains valid.
|
|
666
669
|
*/
|
|
667
670
|
export function readCompleteScoutTargetSurface(records) {
|
|
668
671
|
const fact = latestCommittedScoutTargetSurface(records);
|
|
@@ -704,11 +707,35 @@ export function readCompleteScoutTargetSurface(records) {
|
|
|
704
707
|
.map((item) => asString(item.path))
|
|
705
708
|
.filter(Boolean)
|
|
706
709
|
: []);
|
|
707
|
-
|
|
710
|
+
const pathEvidence = Array.isArray(fact.pathEvidence)
|
|
711
|
+
? fact.pathEvidence.filter(isRecord)
|
|
712
|
+
: [];
|
|
713
|
+
const hasSourceDeclarationLedger = Array.isArray(fact.sourceDeclaredPaths);
|
|
714
|
+
const undeclaredFuturePaths = hasSourceDeclarationLedger
|
|
715
|
+
? namedPaths.filter((candidate) => {
|
|
716
|
+
const evidence = pathEvidence.find((item) => asString(item.path) === candidate);
|
|
717
|
+
return evidence?.fresh !== true && evidence?.sourceDeclared !== true;
|
|
718
|
+
})
|
|
719
|
+
: [];
|
|
720
|
+
if (undeclaredFuturePaths.length > 0)
|
|
708
721
|
return {
|
|
709
722
|
ok: false,
|
|
710
|
-
reason:
|
|
723
|
+
reason: `frontend scout target surface has future paths not declared by the task source: ${undeclaredFuturePaths.join(", ")}; test path is not declared by the task source, so do not guess test/ vs tests/ or other target directories`,
|
|
711
724
|
};
|
|
725
|
+
if (!namedPaths.some((candidate) => freshPaths.has(candidate))) {
|
|
726
|
+
const allPathsAreSourceDeclaredFuturePaths = hasSourceDeclarationLedger &&
|
|
727
|
+
namedPaths.length > 0 &&
|
|
728
|
+
namedPaths.every((candidate) => {
|
|
729
|
+
const evidence = pathEvidence.find((item) => asString(item.path) === candidate);
|
|
730
|
+
return evidence?.fresh !== true && evidence?.sourceDeclared === true;
|
|
731
|
+
});
|
|
732
|
+
if (allPathsAreSourceDeclaredFuturePaths)
|
|
733
|
+
return { ok: true, surface };
|
|
734
|
+
return {
|
|
735
|
+
ok: false,
|
|
736
|
+
reason: "frontend scout target surface lacks fresh runtime evidence for a named target path; greenfield exceptions require runtime-injected source declarations for every future path",
|
|
737
|
+
};
|
|
738
|
+
}
|
|
712
739
|
return { ok: true, surface };
|
|
713
740
|
}
|
|
714
741
|
/** Paths with Scout ownership that Plan may read for a specific decision. */
|