@tea-agent/loop-agent 0.16.25 → 0.17.0
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 +46 -0
- package/README.md +14 -3
- package/dist/cli/command-definitions.js +43 -0
- package/dist/cli/program.js +26 -0
- package/dist/commands/dag-approve.js +4 -0
- package/dist/commands/dag-resume.js +1 -0
- package/dist/commands/dag-validate.js +6 -0
- package/dist/commands/operator.js +44 -0
- package/dist/commands/task-contract.js +271 -0
- package/dist/executors/dag-pi-executor.js +118 -16
- package/dist/executors/pi-executor.js +206 -13
- package/dist/executors/pi-sdk-executor.js +21 -6
- package/dist/executors/shell-executor.js +85 -8
- package/dist/executors/shell-presets.js +16 -3
- package/dist/executors/shell-write-guard.js +64 -2
- package/dist/shared/operator/capabilities.js +255 -0
- package/dist/shared/operator/envelope.js +59 -0
- package/dist/shared/operator/index.js +4 -0
- package/dist/shared/operator/registry.js +38 -0
- package/dist/shared/operator/types.js +5 -0
- package/dist/task/contract/adopt.js +166 -0
- package/dist/task/contract/apply.js +326 -0
- package/dist/task/contract/canonicalize.js +60 -0
- package/dist/task/contract/constants.js +29 -0
- package/dist/task/contract/diff.js +177 -0
- package/dist/task/contract/hash.js +42 -0
- package/dist/task/contract/import-revision.js +96 -0
- package/dist/task/contract/index.js +17 -0
- package/dist/task/contract/journal.js +155 -0
- package/dist/task/contract/lock.js +153 -0
- package/dist/task/contract/observe.js +296 -0
- package/dist/task/contract/paths.js +19 -0
- package/dist/task/contract/project.js +170 -0
- package/dist/task/contract/recover.js +312 -0
- package/dist/task/contract/request-ledger.js +37 -0
- package/dist/task/contract/schema.js +151 -0
- package/dist/task/contract/transaction.js +160 -0
- package/dist/task/contract/types.js +1 -0
- package/dist/task/contract/validate-draft.js +106 -0
- package/dist/task/index.js +3 -0
- package/dist/task/operator/capabilities.js +6 -0
- package/dist/task/operator/envelope.js +2 -0
- package/dist/task/operator/index.js +5 -0
- package/dist/task/operator/registry.js +2 -0
- package/dist/task/operator/types.js +1 -0
- package/dist/task/runtime.js +5 -1
- package/dist/task/source-references.js +7 -0
- package/dist/worker/cli.js +150 -32
- package/dist/worker/console/app-data.js +185 -0
- package/dist/worker/console/dag-confirmation.js +313 -0
- package/dist/worker/console/doctor.js +169 -0
- package/dist/worker/console/draft-store.js +80 -0
- package/dist/worker/console/index.js +15 -0
- package/dist/worker/console/interview/assessment.js +67 -0
- package/dist/worker/console/interview/session.js +100 -0
- package/dist/worker/console/interview/tools.js +109 -0
- package/dist/worker/console/loopback.js +16 -0
- package/dist/worker/console/observe-health-match.js +174 -0
- package/dist/worker/console/observe-link.js +33 -0
- package/dist/worker/console/operation-runner.js +166 -0
- package/dist/worker/console/operation-sse.js +158 -0
- package/dist/worker/console/operation-store.js +147 -0
- package/dist/worker/console/operator-actions.js +769 -0
- package/dist/worker/console/pi-readiness.js +94 -0
- package/dist/worker/console/recovery-cta.js +133 -0
- package/dist/worker/console/repo-fingerprint.js +29 -0
- package/dist/worker/console/resource-loader.js +95 -0
- package/dist/worker/console/routes.js +368 -0
- package/dist/worker/console/security.js +126 -0
- package/dist/worker/console/server.js +149 -0
- package/dist/worker/console/sibling-controller.js +28 -0
- package/dist/worker/console/static/assets/index-CbnMgdWa.js +9 -0
- package/dist/worker/console/static/assets/index-Dnj0RVs8.css +1 -0
- package/dist/worker/console/static/index.html +13 -0
- package/dist/worker/console/vite.config.js +27 -0
- package/dist/worker/delivery/git-transaction.js +43 -8
- package/dist/worker/observe/health.js +57 -0
- package/dist/worker/observe/paths.js +81 -0
- package/dist/worker/observe/routes.js +142 -27
- package/dist/worker/observe/spec-evidence.js +84 -0
- package/dist/worker/observe/static/api.js +23 -0
- package/dist/worker/observe/static/state.js +26 -0
- package/dist/worker/observe/static/styles.css +10 -0
- package/dist/worker/observe/static/views/dag-inspector.js +173 -6
- package/dist/workflows/dag/backend-test-analysis-contract.js +34 -9
- package/dist/workflows/dag/dynamic-runtime/shared.js +1 -0
- package/dist/workflows/dag/frontend-repair.js +1 -10
- package/dist/workflows/dag/init-hybrid.js +372 -115
- package/dist/workflows/dag/node-execution.js +57 -6
- package/dist/workflows/dag/project-governance-context.js +508 -0
- package/dist/workflows/dag/prompt.js +46 -1
- package/dist/workflows/dag/retry-policy.js +16 -1
- package/dist/workflows/dag/runner.js +9 -0
- package/dist/workflows/dag/skill-snapshot.js +1 -0
- package/dist/workflows/dag/task-contract-binding.js +138 -0
- package/dist/workflows/dag/types.js +84 -10
- package/dist/workflows/dag/validate.js +53 -7
- package/docs/README.md +2 -0
- package/docs/architecture/evolution.md +2 -0
- package/docs/architecture/system-overview.md +6 -0
- package/docs/architecture/worker-and-feature.md +7 -0
- package/docs/templates/agent-dag.schema.json +64 -2
- package/docs/templates/agent-dag.supervised-implementation.json +1 -0
- package/docs/templates/backend-test-dag.classify.prompt.md +1 -1
- package/docs/templates/backend-test-dag.generate-pytest.prompt.md +5 -5
- package/docs/templates/backend-test-dag.json +26 -154
- package/docs/templates/backend-test-dag.retrospect.prompt.md +1 -1
- package/docs/templates/backend-test-dag.review-cases.prompt.md +2 -2
- package/package.json +8 -2
- package/skills/agent-worker/SKILL.md +1 -0
- package/skills/agent-worker/references/agent-worker-operator.md +3 -2
- package/skills/frontend-design-review/SKILL.md +25 -16
- package/skills/frontend-implementation/references/node-contracts.md +5 -5
- package/skills/loop-agent/references/command-reference.md +48 -1
- package/skills/loop-agent/references/hybrid-dag.md +4 -4
|
@@ -10,12 +10,14 @@ import {
|
|
|
10
10
|
UI_TEXT,
|
|
11
11
|
} from "../constants.js";
|
|
12
12
|
import { clearNode, el } from "../dom.js";
|
|
13
|
-
import { fetchJson } from "../api.js";
|
|
13
|
+
import { fetchJson, fetchSpecEvidenceFile } from "../api.js";
|
|
14
14
|
import { badge } from "../format.js";
|
|
15
15
|
import { renderMarkdown } from "../markdown-render.js";
|
|
16
16
|
import {
|
|
17
17
|
uiState,
|
|
18
18
|
makeSessionEventIdentity,
|
|
19
|
+
openSpecEvidenceDetail,
|
|
20
|
+
closeSpecEvidenceDetail,
|
|
19
21
|
updateDagTimelineViewportState,
|
|
20
22
|
updateDagOutputViewportState,
|
|
21
23
|
computeFollowLatest,
|
|
@@ -44,6 +46,10 @@ export function selectDagNode(dagRunId, nodeId) {
|
|
|
44
46
|
if (nodeChanged) {
|
|
45
47
|
uiState.sessionEventOffset = 0;
|
|
46
48
|
uiState.sessionEvents = [];
|
|
49
|
+
// Drop any open spec-evidence preview so a stale A detail cannot survive
|
|
50
|
+
// into node B's spec-evidence panel (AC-007 regression guard).
|
|
51
|
+
uiState.specEvidenceDetail = null;
|
|
52
|
+
uiState.specEvidenceListEvidence = null;
|
|
47
53
|
// Bump the session-event identity token so any in-flight response for the
|
|
48
54
|
// previous node is rejected before it can mutate the current node cache.
|
|
49
55
|
uiState.sessionEventIdentity = makeSessionEventIdentity(dagRunId, nodeId);
|
|
@@ -188,15 +194,20 @@ export function captureDagTimelineViewportState(panel) {
|
|
|
188
194
|
}
|
|
189
195
|
}
|
|
190
196
|
|
|
191
|
-
async function renderSpecEvidence(content, dagRunId, nodeId) {
|
|
192
|
-
const evidence = await fetchJson(
|
|
197
|
+
async function renderSpecEvidence(content, dagRunId, nodeId, cachedEvidence) {
|
|
198
|
+
const evidence = cachedEvidence ?? await fetchJson(
|
|
193
199
|
`/api/dag-runs/${encodeURIComponent(dagRunId)}/nodes/${encodeURIComponent(nodeId)}/spec-evidence`,
|
|
194
200
|
);
|
|
195
201
|
if (!content.isConnected || content.dataset.dagNodeId !== String(nodeId)) return;
|
|
202
|
+
if (uiState.specEvidenceDetail) {
|
|
203
|
+
renderSpecEvidenceDetail(content, dagRunId, nodeId);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
196
206
|
if (!evidence) {
|
|
197
207
|
content.appendChild(el("p", "empty", "无法加载规范证据。"));
|
|
198
208
|
return;
|
|
199
209
|
}
|
|
210
|
+
uiState.specEvidenceListEvidence = evidence;
|
|
200
211
|
|
|
201
212
|
const statusLabels = {
|
|
202
213
|
"spec-read": "已读取规范",
|
|
@@ -241,9 +252,24 @@ async function renderSpecEvidence(content, dagRunId, nodeId) {
|
|
|
241
252
|
});
|
|
242
253
|
appendListSection("已绑定任务源", evidence.sourceBinding?.sources, (source) => {
|
|
243
254
|
const item = el("li", null);
|
|
255
|
+
const button = document.createElement("button");
|
|
256
|
+
button.type = "button";
|
|
257
|
+
button.className = "spec-evidence-file-btn";
|
|
258
|
+
button.setAttribute("data-source", "binding");
|
|
259
|
+
button.setAttribute("data-path", source.path);
|
|
260
|
+
button.id = `spec-evidence-btn-binding-${source.path}`;
|
|
244
261
|
const icon = el("i", "ri-links-line");
|
|
245
262
|
icon.setAttribute("aria-hidden", "true");
|
|
246
|
-
|
|
263
|
+
button.append(
|
|
264
|
+
icon,
|
|
265
|
+
document.createTextNode(` ${source.kind}: `),
|
|
266
|
+
el("code", null, source.path),
|
|
267
|
+
document.createTextNode(` · sha256 ${source.sha256.slice(0, 12)}…`),
|
|
268
|
+
);
|
|
269
|
+
button.addEventListener("click", () =>
|
|
270
|
+
onSpecEvidenceFileClick(content, dagRunId, nodeId, "binding", source.path, button),
|
|
271
|
+
);
|
|
272
|
+
item.appendChild(button);
|
|
247
273
|
return item;
|
|
248
274
|
});
|
|
249
275
|
appendListSection("显式需求编号", evidence.sourceBinding?.requirementIds, (id) => el("li", null, id));
|
|
@@ -252,11 +278,20 @@ async function renderSpecEvidence(content, dagRunId, nodeId) {
|
|
|
252
278
|
evidence.specReads,
|
|
253
279
|
(read) => {
|
|
254
280
|
const item = el("li", null);
|
|
281
|
+
const button = document.createElement("button");
|
|
282
|
+
button.type = "button";
|
|
283
|
+
button.className = "spec-evidence-file-btn";
|
|
284
|
+
button.setAttribute("data-source", "read");
|
|
285
|
+
button.setAttribute("data-path", read.path);
|
|
286
|
+
if (read.timestamp) {
|
|
287
|
+
button.setAttribute("data-read-at", read.timestamp);
|
|
288
|
+
}
|
|
289
|
+
button.id = `spec-evidence-btn-read-${read.path}`;
|
|
255
290
|
const icon = el("i", "ri-file-text-line");
|
|
256
291
|
icon.setAttribute("aria-hidden", "true");
|
|
257
|
-
|
|
292
|
+
button.append(icon, el("code", null, read.path));
|
|
258
293
|
if (read.timestamp) {
|
|
259
|
-
|
|
294
|
+
button.appendChild(
|
|
260
295
|
el(
|
|
261
296
|
"span",
|
|
262
297
|
"spec-evidence-time",
|
|
@@ -264,6 +299,10 @@ async function renderSpecEvidence(content, dagRunId, nodeId) {
|
|
|
264
299
|
),
|
|
265
300
|
);
|
|
266
301
|
}
|
|
302
|
+
button.addEventListener("click", () =>
|
|
303
|
+
onSpecEvidenceFileClick(content, dagRunId, nodeId, "read", read.path, button),
|
|
304
|
+
);
|
|
305
|
+
item.appendChild(button);
|
|
267
306
|
return item;
|
|
268
307
|
},
|
|
269
308
|
);
|
|
@@ -308,7 +347,135 @@ async function renderSpecEvidence(content, dagRunId, nodeId) {
|
|
|
308
347
|
}
|
|
309
348
|
}
|
|
310
349
|
|
|
350
|
+
/**
|
|
351
|
+
* Click handler for spec-evidence file buttons (AC-001/002). Opens the detail
|
|
352
|
+
* view in the same spec-evidence panel (no modal), records the trigger button
|
|
353
|
+
* so the back action can restore keyboard focus (AC-005).
|
|
354
|
+
*/
|
|
355
|
+
function onSpecEvidenceFileClick(content, dagRunId, nodeId, source, relPath, button) {
|
|
356
|
+
openSpecEvidenceDetail(source, relPath, button);
|
|
357
|
+
clearNode(content);
|
|
358
|
+
const detail = uiState.specEvidenceDetail;
|
|
359
|
+
renderSpecEvidenceDetail(content, dagRunId, nodeId);
|
|
360
|
+
void (async () => {
|
|
361
|
+
try {
|
|
362
|
+
const data = await fetchSpecEvidenceFile(dagRunId, nodeId, source, relPath);
|
|
363
|
+
if (
|
|
364
|
+
!content.isConnected ||
|
|
365
|
+
content.dataset.dagNodeId !== String(nodeId) ||
|
|
366
|
+
!uiState.specEvidenceDetail ||
|
|
367
|
+
uiState.specEvidenceDetail.source !== source ||
|
|
368
|
+
uiState.specEvidenceDetail.path !== relPath
|
|
369
|
+
) {
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
detail.loading = false;
|
|
373
|
+
detail.data = data;
|
|
374
|
+
} catch (error) {
|
|
375
|
+
if (
|
|
376
|
+
!content.isConnected ||
|
|
377
|
+
content.dataset.dagNodeId !== String(nodeId) ||
|
|
378
|
+
!uiState.specEvidenceDetail ||
|
|
379
|
+
uiState.specEvidenceDetail.source !== source ||
|
|
380
|
+
uiState.specEvidenceDetail.path !== relPath
|
|
381
|
+
) {
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
detail.loading = false;
|
|
385
|
+
detail.error = error instanceof Error ? error.message : String(error);
|
|
386
|
+
}
|
|
387
|
+
renderSpecEvidenceDetail(content, dagRunId, nodeId);
|
|
388
|
+
})();
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Render the in-inspector file preview (AC-005). Single scroll container for
|
|
393
|
+
* the body, always-visible back button, and accessible loading/error states.
|
|
394
|
+
*/
|
|
395
|
+
function renderSpecEvidenceDetail(content, dagRunId, nodeId) {
|
|
396
|
+
const detail = uiState.specEvidenceDetail;
|
|
397
|
+
clearNode(content);
|
|
398
|
+
if (!detail) {
|
|
399
|
+
renderSpecEvidence(content, dagRunId, nodeId, uiState.specEvidenceListEvidence);
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const wrapper = el("div", "spec-evidence-detail");
|
|
404
|
+
const back = document.createElement("button");
|
|
405
|
+
back.type = "button";
|
|
406
|
+
back.className = "spec-evidence-back";
|
|
407
|
+
back.setAttribute("aria-label", "返回规范证据");
|
|
408
|
+
const backIcon = el("i", "ri-arrow-left-line");
|
|
409
|
+
backIcon.setAttribute("aria-hidden", "true");
|
|
410
|
+
back.append(backIcon, document.createTextNode(" 返回规范证据"));
|
|
411
|
+
back.addEventListener("click", () => {
|
|
412
|
+
const closed = closeSpecEvidenceDetail();
|
|
413
|
+
clearNode(content);
|
|
414
|
+
// Restore the list synchronously from the cached evidence so the user
|
|
415
|
+
// sees no flicker and the trigger button is immediately focusable.
|
|
416
|
+
renderSpecEvidence(content, dagRunId, nodeId, uiState.specEvidenceListEvidence);
|
|
417
|
+
if (closed?.triggerId) {
|
|
418
|
+
const trigger = document.getElementById(closed.triggerId);
|
|
419
|
+
trigger?.focus();
|
|
420
|
+
}
|
|
421
|
+
});
|
|
422
|
+
wrapper.appendChild(back);
|
|
423
|
+
|
|
424
|
+
const meta = el("div", "spec-evidence-detail-meta");
|
|
425
|
+
meta.appendChild(el("code", null, detail.path));
|
|
426
|
+
if (detail.source === "binding" && detail.data) {
|
|
427
|
+
meta.appendChild(
|
|
428
|
+
el(
|
|
429
|
+
"span",
|
|
430
|
+
"spec-evidence-detail-hash",
|
|
431
|
+
`绑定 sha256 ${String(detail.data.sha256 ?? "").slice(0, 12)}… · 当前 ${detail.data.hashMatch ? "一致" : "不一致"}`,
|
|
432
|
+
),
|
|
433
|
+
);
|
|
434
|
+
} else if (detail.source === "read" && detail.data?.readAt) {
|
|
435
|
+
meta.appendChild(
|
|
436
|
+
el(
|
|
437
|
+
"span",
|
|
438
|
+
"spec-evidence-detail-time",
|
|
439
|
+
`读取于 ${formatSessionEventTime({ timestamp: detail.data.readAt })}`,
|
|
440
|
+
),
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
wrapper.appendChild(meta);
|
|
444
|
+
|
|
445
|
+
if (detail.loading) {
|
|
446
|
+
const loading = el("p", "muted", "正在加载文件内容…");
|
|
447
|
+
loading.setAttribute("role", "status");
|
|
448
|
+
loading.setAttribute("aria-live", "polite");
|
|
449
|
+
wrapper.appendChild(loading);
|
|
450
|
+
} else if (detail.error) {
|
|
451
|
+
const error = el("p", "spec-evidence-detail-error", `加载失败:${detail.error}`);
|
|
452
|
+
error.setAttribute("role", "alert");
|
|
453
|
+
wrapper.appendChild(error);
|
|
454
|
+
} else if (detail.data) {
|
|
455
|
+
const body = el("pre", "spec-evidence-detail-content");
|
|
456
|
+
body.textContent = detail.data.content ?? "";
|
|
457
|
+
wrapper.appendChild(body);
|
|
458
|
+
if (detail.data.truncated) {
|
|
459
|
+
const note = el(
|
|
460
|
+
"p",
|
|
461
|
+
"spec-evidence-detail-truncated",
|
|
462
|
+
`已截断,仅显示前 ${detail.data.maxBytes ?? 0} 字节。`,
|
|
463
|
+
);
|
|
464
|
+
wrapper.appendChild(note);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
content.appendChild(wrapper);
|
|
468
|
+
}
|
|
469
|
+
|
|
311
470
|
function fillDagInspectorContent(content, dagRunId, node) {
|
|
471
|
+
const previousRenderedNode = content.dataset.dagNodeId ?? "";
|
|
472
|
+
const nodeChanged = previousRenderedNode !== String(node.nodeId ?? "");
|
|
473
|
+
if (nodeChanged) {
|
|
474
|
+
// Drop any open spec-evidence preview + cached list so a stale A detail
|
|
475
|
+
// cannot survive into node B (AC-007 regression guard).
|
|
476
|
+
uiState.specEvidenceDetail = null;
|
|
477
|
+
uiState.specEvidenceListEvidence = null;
|
|
478
|
+
}
|
|
312
479
|
const activeTab = ["timeline", "spec-evidence"].includes(
|
|
313
480
|
uiState.dagInspectorTab,
|
|
314
481
|
)
|
|
@@ -356,20 +356,47 @@ function coerceOptionalEvidence(value, kind) {
|
|
|
356
356
|
out.sourceRef = sourceRef;
|
|
357
357
|
return out;
|
|
358
358
|
}
|
|
359
|
+
const EVIDENCE_GAP_INPUT_KEYS = new Set([
|
|
360
|
+
"description",
|
|
361
|
+
"text",
|
|
362
|
+
"requirementId",
|
|
363
|
+
"acId",
|
|
364
|
+
"id",
|
|
365
|
+
"sourceRef",
|
|
366
|
+
"sourceRefs",
|
|
367
|
+
]);
|
|
359
368
|
function coerceEvidenceGap(value) {
|
|
369
|
+
if (typeof value === "string") {
|
|
370
|
+
if (!value.trim())
|
|
371
|
+
throw new Error("evidence gap string must be non-empty");
|
|
372
|
+
return { description: value.trim() };
|
|
373
|
+
}
|
|
360
374
|
const record = asRecord(value);
|
|
361
375
|
if (!record)
|
|
362
|
-
|
|
376
|
+
throw new Error("evidence gap must be a string or object");
|
|
377
|
+
for (const key of Object.keys(record)) {
|
|
378
|
+
if (!EVIDENCE_GAP_INPUT_KEYS.has(key))
|
|
379
|
+
throw new Error(`evidence gap has unrecognized key: ${key}`);
|
|
380
|
+
}
|
|
381
|
+
for (const key of ["description", "text", "requirementId", "acId", "id", "sourceRef"]) {
|
|
382
|
+
if (record[key] !== undefined && typeof record[key] !== "string")
|
|
383
|
+
throw new Error(`evidence gap ${key} must be a string`);
|
|
384
|
+
}
|
|
385
|
+
if (record.sourceRefs !== undefined && (!Array.isArray(record.sourceRefs) || record.sourceRefs.some((item) => typeof item !== "string"))) {
|
|
386
|
+
throw new Error("evidence gap sourceRefs must be a string array");
|
|
387
|
+
}
|
|
363
388
|
const description = (typeof record.description === "string" && record.description.trim()) ||
|
|
364
389
|
(typeof record.text === "string" && record.text.trim()) ||
|
|
365
390
|
"";
|
|
366
391
|
if (!description)
|
|
367
|
-
|
|
392
|
+
throw new Error("evidence gap description must be non-empty");
|
|
368
393
|
const out = { description };
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
394
|
+
const requirementId = (typeof record.requirementId === "string" && record.requirementId.trim()) ||
|
|
395
|
+
(typeof record.acId === "string" && record.acId.trim()) ||
|
|
396
|
+
(typeof record.id === "string" && record.id.trim()) ||
|
|
397
|
+
"";
|
|
398
|
+
if (requirementId)
|
|
399
|
+
out.requirementId = requirementId;
|
|
373
400
|
const sourceRef = firstSourceRef(record, "");
|
|
374
401
|
if (sourceRef)
|
|
375
402
|
out.sourceRef = sourceRef;
|
|
@@ -403,9 +430,7 @@ export function coerceBackendTestAnalysisInput(value) {
|
|
|
403
430
|
next.risks = asArray(record.risks)
|
|
404
431
|
.map((item) => coerceOptionalEvidence(item, "risk"))
|
|
405
432
|
.filter((item) => item !== null);
|
|
406
|
-
next.evidenceGaps = asArray(record.evidenceGaps)
|
|
407
|
-
.map(coerceEvidenceGap)
|
|
408
|
-
.filter((item) => item !== null);
|
|
433
|
+
next.evidenceGaps = asArray(record.evidenceGaps).map(coerceEvidenceGap);
|
|
409
434
|
return next;
|
|
410
435
|
}
|
|
411
436
|
function normalizeAnalysis(value) {
|
|
@@ -320,16 +320,7 @@ export async function runFrontendRepairContractGate(input) {
|
|
|
320
320
|
return { ok: true, assessment };
|
|
321
321
|
}
|
|
322
322
|
export function formatFrontendFailureAssessStdout(assessment) {
|
|
323
|
-
return
|
|
324
|
-
"Frontend failure assess: ok",
|
|
325
|
-
`eligible=${assessment.eligible}`,
|
|
326
|
-
`failureClass=${assessment.failureClass}`,
|
|
327
|
-
`failedNodeIds=${assessment.failedNodeIds.join(",") || "(none)"}`,
|
|
328
|
-
`attempt=${assessment.attempt}/${assessment.maxAttempts}`,
|
|
329
|
-
`reason=${assessment.reason}`,
|
|
330
|
-
`browser=${assessment.browserStatus}`,
|
|
331
|
-
`artifact=contracts/frontend-repair-assessment.json`,
|
|
332
|
-
].join("\n");
|
|
323
|
+
return JSON.stringify(assessment);
|
|
333
324
|
}
|
|
334
325
|
export function formatFrontendRepairContractStdout(assessment) {
|
|
335
326
|
return [
|