@tea-agent/loop-agent 0.43.0-next.16 → 0.43.0-next.18
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 +8 -1
- package/dist/application/dag/args.js +8 -0
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +2 -0
- package/dist/cli/program.js +3 -1
- package/dist/cli/update/init-surface-notifier.js +2 -0
- package/dist/cli/update/policy.js +2 -0
- package/dist/commands/cursor-prompt.js +2 -0
- package/dist/commands/dag-approve.js +2 -0
- package/dist/commands/dag-final-verification.js +2 -0
- package/dist/commands/dag-init-hybrid.js +2 -0
- package/dist/commands/dag-reconcile-tasks.js +2 -0
- package/dist/commands/dag-reject.js +2 -0
- package/dist/commands/dag-report.js +2 -0
- package/dist/commands/dag-rerun.js +2 -0
- package/dist/commands/dag-resume.js +2 -0
- package/dist/commands/dag-workflow-compile.js +2 -0
- package/dist/commands/dag-workflow-plan.js +2 -0
- package/dist/commands/dag-workflow-validate.js +2 -0
- package/dist/commands/delegate.js +2 -0
- package/dist/commands/examples.js +2 -0
- package/dist/commands/import-prd.js +2 -0
- package/dist/commands/init-model-catalog.js +2 -0
- package/dist/commands/init.js +6 -4
- package/dist/commands/instructions.js +2 -0
- package/dist/commands/knowledge.js +2 -0
- package/dist/commands/loop-benchmark.js +2 -0
- package/dist/commands/pi-prompt.js +2 -0
- package/dist/commands/pi-reuse-benchmark.js +2 -0
- package/dist/commands/promote-run.js +2 -0
- package/dist/commands/stats.js +1 -1
- package/dist/commands/study-init.js +2 -0
- package/dist/commands/task-advance.js +2 -0
- package/dist/commands/task-source-prepare.js +2 -0
- package/dist/commands/worktree-create.js +2 -0
- package/dist/commands/worktree-remove.js +2 -0
- package/dist/executors/dag-pi-executor.js +25 -6
- package/dist/executors/shell-verification.js +3 -0
- package/dist/governance/exec-plans.js +6 -3
- package/dist/infrastructure/console/app-data.js +6 -0
- package/dist/shared/artifacts-core.js +2 -0
- package/dist/shared/git-progress.js +2 -0
- package/dist/shared/one-shot-prompt-args.js +2 -0
- package/dist/shared/package-metadata.js +6 -4
- package/dist/shared/pi-context-pressure/sift-bridge.js +1 -1
- package/dist/shared/reference-context.js +2 -0
- package/dist/task/contract/recover.js +6 -0
- package/dist/task/source-prepare/artifact-meta.js +9 -2
- package/dist/task/source-prepare/fragment-inventory.js +4 -1
- package/dist/task/source-prepare/parse-intent.js +36 -11
- package/dist/task/task-demand-routing.js +5 -2
- package/dist/worker/console/chat/browser-automation.js +468 -308
- package/dist/worker/console/chat/browser-routes.js +175 -57
- package/dist/worker/console/chat/explore-tools.js +5 -2
- package/dist/worker/console/chat/pi-runtime/custom-tools/browser-tools.js +115 -20
- package/dist/worker/console/chat/pi-runtime.js +9 -1
- package/dist/worker/console/chat/routes.js +1 -1
- package/dist/worker/console/chat/sdd-data-alignment.js +1 -1
- package/dist/worker/console/chat/session-mode.js +1 -1
- package/dist/worker/console/chat/tools.js +7 -1
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-C4NXyedy.js → abnfDiagram-N423BO3Z-B_UDknhR.js} +1 -1
- package/dist/worker/console/static/assets/{arc-NZ3qXvBI.js → arc-D6hU0drN.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-COZSRvml.js → architectureDiagram-T3A2C74G-DmVikdRK.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-Ck0Gxi9C.js → blockDiagram-VBNYF7ZC-DydABCRg.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-BX5HQ4Ox.js → c4Diagram-5PPSVZJV-DrXKC2Jl.js} +1 -1
- package/dist/worker/console/static/assets/channel-Drecd94a.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-CoviemSL.js → chunk-2GRJ4B5K-D2-qFxr7.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-DbOdOkbZ.js → chunk-2Q5K7J3B-ChfeF41P.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-BsG2pmdQ.js → chunk-5RXB4S5H-Drwrz55x.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-DGdvrNm8.js → chunk-5VM5RSS4-CAQGErL7.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-BAy7Yd6y.js → chunk-6Q2QTUOP-DHO38w5U.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-D9UA7hUb.js → chunk-GF5L2VYU-EYIN0EOI.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-DPz7SZ8V.js → chunk-JWPE2WC7-CrFLKqkT.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-BmuEOIh7.js → chunk-KBJHAD2P-DMchffoo.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-CIhBWnd8.js → chunk-RYQCIY6F-DaSvtKQa.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-CScR6mDD.js → chunk-XXDRQBXY-DcW8W9Qj.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-8Udu0t8-.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-8Udu0t8-.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BjpfWaUr.js → cose-bilkent-JH36ORCC-BUirrMEi.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BFQohJgT.js → cynefin-VYW2F7L2-BXzO8iXR.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-D3gzRJnS.js → cynefinDiagram-MW4NZA55-DAtgaxIn.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-5eojkhoA.js → dagre-VZM6K2ZE-C1re9Noh.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-CpD8LWu_.js → diagram-7IWD3JNH-N8Kj08J9.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-B8_4qJ2a.js → diagram-B4RE2ZJO-D6BpSLs3.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-HOsXf0eu.js → diagram-LBJQPF4R-C2Vlgnl4.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-BqwRwiWP.js → diagram-Q27KOJAE-CMbqtVLS.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-BRVCNZe1.js → diagram-UB23O5K3-A770Eb-0.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-Cvb1TgGh.js → ebnfDiagram-BXEA7PRR-LV2w_2pT.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-DS0O0qG9.js → erDiagram-JOGREHBK-bwqf56ah.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-BHIRvRr3.js → flowDiagram-UKHOOZJN-BnVtHhZh.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-DcCJ1JWx.js → ganttDiagram-PKOTCBZU-BGVToEqc.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-B9ia5ony.js → gitGraphDiagram-DS77QQ5N-DL2l7Vne.js} +1 -1
- package/dist/worker/console/static/assets/{index-wXlQohNs.js → index-B_V4wvXs.js} +44 -44
- package/dist/worker/console/static/assets/{index-B9goN3S5.css → index-DcudonhZ.css} +1 -1
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-3ogpgegn.js → infoDiagram-6WML65LV-BWIpubOx.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-4tgpQ5SL.js → ishikawaDiagram-WSZJBQD7-DjYBD8vv.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DNfgHqFx.js → journeyDiagram-NVQOT4AX-C0_mBaHb.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-Dz7ngC8z.js → kanban-definition-27J2QSJJ-Cy71zbX6.js} +1 -1
- package/dist/worker/console/static/assets/{linear-DI7Jv62E.js → linear-Dt3_w3Vn.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-CQA4Nzmo.js → mermaid.core-DnNOlfEP.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-CtsMc8Mf.js → mindmap-definition-FAOFIHXS-DAbTspwq.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-ByppE9yH.js → pegDiagram-VL7TDLO6-NNiM141p.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-B5PtgFgt.js → pieDiagram-7S7Q4E2Y-CN80Z1Do.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-Cq4p0k2K.js → quadrantDiagram-CIZ2JOQS-Bxyfh0JU.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-DjfkyljN.js → railroadDiagram-AXF67PYL-BlWnC79M.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-DzynNRSl.js → requirementDiagram-LRYGKXZP-BrEu4P_e.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-Cj7TCpTJ.js → sankeyDiagram-W5VNT64P-Bt70igBr.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-C73C6ykA.js → sequenceDiagram-SI44F4Z6-C8DQR-r9.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-BiNLgN2x.js → sizeCapture-X5ZJPWSS-DEMinJAT.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-UwvjS64-.js → stateDiagram-OKZ733FA-DDdDBzQc.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-CyCiS0Lt.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-CC7DH98r.js → swimlanes-SLNWSIFB-G2wcBs3T.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-U0lhi6N0.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-LSBXTR70.js → timeline-definition-Z64GVDOM-Dzn4g2gJ.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-BAuxWwGP.js → vennDiagram-T6HMQDX7-5v2rP9oO.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y--E5_gjZx.js → wardleyDiagram-T6FBY63Y-B4EOx7ew.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-dJWNmyzF.js → xychartDiagram-ELKLHX3M-CQ1v8eYP.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/operator-chat/slash-palette-nav.js +15 -5
- package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +2 -0
- package/dist/worker/console/static-src/prd-file-import.js +3 -1
- package/dist/worker/console/workspace-context.js +6 -1
- package/dist/worker/delivery/git-transaction.js +2 -0
- package/dist/worker/feature/fullstack-validate.js +2 -0
- package/dist/worker/loop-agent/loop-agent-client.js +8 -2
- package/dist/worker/materialize/harness-task-materializer.js +4 -1
- package/dist/worker/metrics/projector.js +1 -1
- package/dist/worker/observability/event-store.js +4 -1
- package/dist/worker/observability/read-model.js +4 -1
- package/dist/worker/observe/node-transparency.js +23 -1
- package/dist/worker/observe/routes.js +4 -0
- package/dist/worker/outcomes/declared-artifacts.js +2 -0
- package/dist/worker/preflight.js +3 -0
- package/dist/worker/task-spec/validate.js +4 -1
- package/dist/workflows/dag/decision-envelope.js +2 -0
- package/dist/workflows/dag/dynamic-runtime/shared.js +2 -2
- package/dist/workflows/dag/frontend-verification-trace.js +14 -10
- package/dist/workflows/dag/init-hybrid.js +105 -11
- package/dist/workflows/dag/interrupt-request.js +2 -0
- package/dist/workflows/dag/lifecycle.js +4 -0
- package/dist/workflows/dag/node-execution.js +2 -0
- package/dist/workflows/dag/reconcile-run.js +2 -0
- package/dist/workflows/dag/repair-artifact.js +3 -1
- package/dist/workflows/dag/report.js +2 -0
- package/dist/workflows/dag/rerun-task.js +2 -0
- package/dist/workflows/dag/types.js +21 -6
- package/dist/workflows/dag/validate.js +4 -0
- package/docs/governance/README.md +1 -0
- package/package.json +3 -1
- package/dist/worker/console/static/assets/channel-BxdTlb_5.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-_ByA1Nf8.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-_ByA1Nf8.js +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-nLgMZ0D3.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-BcYqTady.js +0 -8
|
@@ -245,7 +245,10 @@ function checkPathBoundaries(taskSpec, errors, warnings) {
|
|
|
245
245
|
const pathErrors = errors.slice(start).filter((error) => error.layer === "path-boundary");
|
|
246
246
|
if (pathErrors.length === 0)
|
|
247
247
|
return "ok";
|
|
248
|
-
|
|
248
|
+
const [firstPathError] = pathErrors;
|
|
249
|
+
if (firstPathError === undefined)
|
|
250
|
+
return "ok";
|
|
251
|
+
return toFailure(firstPathError);
|
|
249
252
|
}
|
|
250
253
|
function checkVerifyCommands(taskSpec, errors) {
|
|
251
254
|
if (!VERIFY_OPTIONAL_TYPES.has(taskSpec.type) &&
|
|
@@ -31,13 +31,13 @@ export function getNodeOutputAsJson(state, nodeId) {
|
|
|
31
31
|
export function normalizeVerdictCandidateLine(value) {
|
|
32
32
|
const trimmed = String(value).trim();
|
|
33
33
|
const emphasized = trimmed.match(/^(?:`{1,3}|\*{1,3})\s*(VERDICT:[^`*]+?)\s*(?:`{1,3}|\*{1,3})$/);
|
|
34
|
-
const line = (emphasized
|
|
34
|
+
const line = (emphasized?.[1] ?? trimmed).trim();
|
|
35
35
|
// Canonicalize the colon separator and trailing sentence punctuation so
|
|
36
36
|
// "VERDICT:pass", "VERDICT: pass", or "VERDICT: pass." match the declared
|
|
37
37
|
// enum lines without accepting free-text verdicts. Conflicting verdicts
|
|
38
38
|
// still canonicalize to distinct enum lines and remain fail-closed.
|
|
39
39
|
const verdictMatch = line.match(/^VERDICT:\s*(pass|request-revision)\s*[.!?。!?]*$/i);
|
|
40
|
-
if (verdictMatch)
|
|
40
|
+
if (verdictMatch?.[1])
|
|
41
41
|
return `VERDICT: ${verdictMatch[1].toLowerCase()}`;
|
|
42
42
|
return line;
|
|
43
43
|
}
|
|
@@ -170,6 +170,8 @@ export function extractFrontendTestTitles(content) {
|
|
|
170
170
|
}
|
|
171
171
|
for (let index = 0; index < tokens.length; index += 1) {
|
|
172
172
|
const token = tokens[index];
|
|
173
|
+
if (token === undefined)
|
|
174
|
+
continue;
|
|
173
175
|
if (token?.type !== "identifier" ||
|
|
174
176
|
!["describe", "it", "test"].includes(token.value) ||
|
|
175
177
|
(tokens[index - 1]?.type === "punctuation" &&
|
|
@@ -183,15 +185,16 @@ export function extractFrontendTestTitles(content) {
|
|
|
183
185
|
while (tokens[cursor]?.type === "punctuation" &&
|
|
184
186
|
tokens[cursor]?.value === "." &&
|
|
185
187
|
tokens[cursor + 1]?.type === "identifier") {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
+
const modifier = tokens[cursor + 1];
|
|
189
|
+
if (modifier === undefined)
|
|
190
|
+
break;
|
|
191
|
+
if (configurationModifiers.has(modifier.value)) {
|
|
188
192
|
hasConfigurationModifier = true;
|
|
189
|
-
if (["runIf", "skipIf"].includes(
|
|
190
|
-
conditionalModifier =
|
|
193
|
+
if (["runIf", "skipIf"].includes(modifier.value)) {
|
|
194
|
+
conditionalModifier = modifier.value;
|
|
191
195
|
}
|
|
192
196
|
}
|
|
193
|
-
if (
|
|
194
|
-
nonExecutingModifiers.has(tokens[cursor + 1].value)) {
|
|
197
|
+
if (nonExecutingModifiers.has(modifier.value)) {
|
|
195
198
|
hasNonExecutingModifier = true;
|
|
196
199
|
}
|
|
197
200
|
cursor += 2;
|
|
@@ -221,9 +224,10 @@ export function extractFrontendTestTitles(content) {
|
|
|
221
224
|
index = closingParenIndex(cursor) ?? tokens.length;
|
|
222
225
|
continue;
|
|
223
226
|
}
|
|
224
|
-
|
|
227
|
+
const titleToken = tokens[cursor + 1];
|
|
228
|
+
if (titleToken?.type !== "string")
|
|
225
229
|
continue;
|
|
226
|
-
titles.push(
|
|
230
|
+
titles.push(titleToken.value);
|
|
227
231
|
}
|
|
228
232
|
return titles;
|
|
229
233
|
}
|
|
@@ -326,9 +330,9 @@ export async function evaluateVerificationTargets(input) {
|
|
|
326
330
|
export function verificationSymbolMatchesContent(symbol, content) {
|
|
327
331
|
if (symbol.trim().toLowerCase() === "all describe blocks")
|
|
328
332
|
return content.includes("describe(");
|
|
333
|
+
const separatorHead = symbol.split(/[((]/, 1)[0];
|
|
329
334
|
const symbols = symbol.includes(" / ")
|
|
330
|
-
? symbol
|
|
331
|
-
.split(/[((]/, 1)[0]
|
|
335
|
+
? (separatorHead ?? symbol)
|
|
332
336
|
.split("/")
|
|
333
337
|
.map((item) => item.trim())
|
|
334
338
|
.filter(Boolean)
|
|
@@ -814,7 +814,7 @@ export function extractSuccessCriteria(requirementMarkdown, taskId) {
|
|
|
814
814
|
const criteria = [];
|
|
815
815
|
const sectionMatch = requirementMarkdown.match(SUCCESS_CRITERIA_SECTION);
|
|
816
816
|
if (sectionMatch) {
|
|
817
|
-
for (const line of sectionMatch[2].split("\n")) {
|
|
817
|
+
for (const line of (sectionMatch[2] ?? "").split("\n")) {
|
|
818
818
|
const trimmed = line.replace(/^[-*]\s*\[[ xX]\]\s*/, "").trim();
|
|
819
819
|
if (trimmed.startsWith("-") || trimmed.startsWith("*")) {
|
|
820
820
|
criteria.push(trimmed.replace(/^[-*]\s*/, "").trim());
|
|
@@ -1020,6 +1020,82 @@ function extractFrontendVerifyCommandsFromMarkdown(input) {
|
|
|
1020
1020
|
}
|
|
1021
1021
|
return { staticCommands, behaviorCommands };
|
|
1022
1022
|
}
|
|
1023
|
+
/**
|
|
1024
|
+
* Resolve the static verification commands the frontend DAG will freeze.
|
|
1025
|
+
*
|
|
1026
|
+
* The frontend contract is "the implementation must be statically verifiable -
|
|
1027
|
+
* never a no-op". It used to mean exactly `npm run build`, which made the whole
|
|
1028
|
+
* run fail at the verify node (8 of 11) for any project that legitimately has no
|
|
1029
|
+
* build step: a zero-dependency Node app, a static-site repo, a plain-ESM
|
|
1030
|
+
* browser module. The failure landed *after* `frontend-implement-pi` had already
|
|
1031
|
+
* delivered correct, test-passing work, so the run reported failure for a project
|
|
1032
|
+
* shape that was never going to pass. That is a meaningless failure and a
|
|
1033
|
+
* gratuitous loss of trust, not a safety property.
|
|
1034
|
+
*
|
|
1035
|
+
* Order, keeping every choice a real check:
|
|
1036
|
+
* 1. `npm run build` when the project defines it - unchanged for every project
|
|
1037
|
+
* that already worked.
|
|
1038
|
+
* 2. static commands the task declared via `--verify`. These were already being
|
|
1039
|
+
* classified onto the static lane and then discarded, so an operator who
|
|
1040
|
+
* declared `--verify typecheck:npm run typecheck` was silently ignored.
|
|
1041
|
+
* 3. the project's own `typecheck` / `check` script.
|
|
1042
|
+
* 4. otherwise the historical `npm run build` is kept. That still fails at the
|
|
1043
|
+
* verify node, exactly as before this change - it is deliberately NOT a new
|
|
1044
|
+
* generation-time error. Introducing one would turn a late failure into an
|
|
1045
|
+
* early one for projects that never declared static verification, which is
|
|
1046
|
+
* more failure, not less; the operator can still declare one via `--verify`.
|
|
1047
|
+
*
|
|
1048
|
+
* Lint is deliberately never chosen: the frontend topology hard-codes
|
|
1049
|
+
* `lintShellCommands = []` so existing lint debt stays outside this workflow, and
|
|
1050
|
+
* auto-selecting a lint script would turn that debt into a fresh failure.
|
|
1051
|
+
*/
|
|
1052
|
+
const FRONTEND_STATIC_SCRIPT_PREFERENCE = ["typecheck", "check"];
|
|
1053
|
+
async function resolveFrontendStaticCommands(input) {
|
|
1054
|
+
let scripts = {};
|
|
1055
|
+
try {
|
|
1056
|
+
const manifest = JSON.parse(await readFile(path.join(input.repoRoot, "package.json"), "utf8"));
|
|
1057
|
+
scripts = manifest.scripts ?? {};
|
|
1058
|
+
}
|
|
1059
|
+
catch (error) {
|
|
1060
|
+
if (error.code !== "ENOENT")
|
|
1061
|
+
throw error;
|
|
1062
|
+
}
|
|
1063
|
+
const hasScript = (name) => {
|
|
1064
|
+
const script = scripts[name];
|
|
1065
|
+
return typeof script === "string" && script.trim() !== "";
|
|
1066
|
+
};
|
|
1067
|
+
if (hasScript("build")) {
|
|
1068
|
+
return [
|
|
1069
|
+
{
|
|
1070
|
+
label: "build",
|
|
1071
|
+
args: ["bash", "-lc", "npm run build"],
|
|
1072
|
+
cwd: input.repoRoot,
|
|
1073
|
+
},
|
|
1074
|
+
];
|
|
1075
|
+
}
|
|
1076
|
+
if (input.explicitStatic.length > 0)
|
|
1077
|
+
return input.explicitStatic;
|
|
1078
|
+
for (const name of FRONTEND_STATIC_SCRIPT_PREFERENCE) {
|
|
1079
|
+
if (!hasScript(name))
|
|
1080
|
+
continue;
|
|
1081
|
+
return [
|
|
1082
|
+
{
|
|
1083
|
+
label: name,
|
|
1084
|
+
args: ["bash", "-lc", `npm run ${name}`],
|
|
1085
|
+
cwd: input.repoRoot,
|
|
1086
|
+
},
|
|
1087
|
+
];
|
|
1088
|
+
}
|
|
1089
|
+
// Nothing static is discoverable: keep the historical command rather than
|
|
1090
|
+
// inventing a new failure. See the doc comment above for why.
|
|
1091
|
+
return [
|
|
1092
|
+
{
|
|
1093
|
+
label: "build",
|
|
1094
|
+
args: ["bash", "-lc", "npm run build"],
|
|
1095
|
+
cwd: input.repoRoot,
|
|
1096
|
+
},
|
|
1097
|
+
];
|
|
1098
|
+
}
|
|
1023
1099
|
/** Reuse the project's test entrypoint; never install a runner for an untested project. */
|
|
1024
1100
|
async function resolveFrontendExistingTests(sources) {
|
|
1025
1101
|
const repoRoot = sources.repoRoot ?? process.cwd();
|
|
@@ -1425,7 +1501,7 @@ function isFullSuiteVerifyCommand(command) {
|
|
|
1425
1501
|
(rest.length === 2 && rest[0] === "run" && rest[1] === "test"));
|
|
1426
1502
|
}
|
|
1427
1503
|
if (["bash", "sh"].includes(executable) && rest.length === 1) {
|
|
1428
|
-
return /(?:^|\/)scripts\/(?:ci|check-repo)\.sh$/i.test(rest[0].replace(/\\/g, "/"));
|
|
1504
|
+
return /(?:^|\/)scripts\/(?:ci|check-repo)\.sh$/i.test((rest[0] ?? "").replace(/\\/g, "/"));
|
|
1429
1505
|
}
|
|
1430
1506
|
return (rest.length === 0 &&
|
|
1431
1507
|
/(?:^|\/)scripts\/(?:ci|check-repo)\.sh$/i.test((args[index] ?? "").replace(/\\/g, "/")));
|
|
@@ -2597,11 +2673,27 @@ function resolveFrontendMockContextBlock(sources) {
|
|
|
2597
2673
|
parts.push("Implement the selected Mock strategy within authorized paths and keep production-default-off isolation. Mock behavior verification is deferred to frontend-test; it is not an implementation gate.");
|
|
2598
2674
|
}
|
|
2599
2675
|
if (mode === "not-required") {
|
|
2600
|
-
|
|
2676
|
+
// The assessment sentence is deliberately split: every branch keeps the
|
|
2677
|
+
// "select not-needed when Mock is intentionally skipped" guidance, because
|
|
2678
|
+
// that is the correct action in both cases, and only the permissive
|
|
2679
|
+
// "or select a safe Mock strategy if project evidence supports one" tail is
|
|
2680
|
+
// withheld when the allow-list is frozen.
|
|
2681
|
+
//
|
|
2682
|
+
// Emitting both was a direct instruction conflict, and it cost a whole
|
|
2683
|
+
// frontend run (dogfood R2): the model resolved it the permissive way, the
|
|
2684
|
+
// typed tool accepted ten `native` records with ok:true, and the
|
|
2685
|
+
// design-policy shell rejected the contract ~90s later, killing 8
|
|
2686
|
+
// downstream nodes. The fix belongs here - stop contradicting ourselves -
|
|
2687
|
+
// rather than in extra enforcement that would leave the model guessing.
|
|
2688
|
+
const mockAssessment = "Generation-time evidence does not require Mock. The assessment must still use contract/scout evidence: select not-needed when Mock is intentionally skipped";
|
|
2601
2689
|
if (frontendMockStrategyMustBeNotNeeded(sources)) {
|
|
2690
|
+
parts.push(`${mockAssessment}.`);
|
|
2602
2691
|
parts.push('Auto mode has no confirmed project Mock capability. The structured contract must set mockApi.strategy to "not-needed". Keep the real request path as the default, record any unproved backend behavior as Real Integration Gap, and do not add Mock files or dependencies within this run.');
|
|
2603
2692
|
parts.push('HARD CONSTRAINT (frozen at generation time): this DAG allows only mockApi.strategy "not-needed"; the prewrite gate rejects any other strategy. If project governance (openspec / ai_workspace / decision records, e.g. a DEC rule requiring native) demands Mock-backed verification, that is a generation-time contract gap, not a plan-revision defect: confirm the project Mock capability and policy: "required" in task.json and regenerate the DAG.');
|
|
2604
2693
|
}
|
|
2694
|
+
else {
|
|
2695
|
+
parts.push(`${mockAssessment}, or select a safe Mock strategy if project evidence supports one.`);
|
|
2696
|
+
}
|
|
2605
2697
|
}
|
|
2606
2698
|
if (mode === "blocked") {
|
|
2607
2699
|
parts.push("Mock contract is blocked. The DAG must stop before any write node executes.");
|
|
@@ -3125,7 +3217,7 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
3125
3217
|
"Frontend planning must consume the read-only Mock assessment strategy produced after scouting; MOCK_STRATEGY: blocked must not pass the deterministic Mock contract gate.",
|
|
3126
3218
|
"Mock implementations must preserve the real request path as the default, require explicit test/dev activation, and never rely on commenting out the real request.",
|
|
3127
3219
|
"Mock-backed behavior evidence proves only the documented frontend contract, never real API integration.",
|
|
3128
|
-
"frontend-implementation requires
|
|
3220
|
+
"frontend-implementation requires the frozen static verification command and existing affected unit tests. Update affected tests with the implementation; do not install a test framework when absent. Broader browser/visual testing may be deferred; static-verification success is never test success.",
|
|
3129
3221
|
"Frontend closeout renders only from committed facts; a weak status (failed / not-run / baseline-debt / mock-backed / pending) can never be rewritten into a stronger one (passed / real-integrated).",
|
|
3130
3222
|
`Frontend risk classification: ${frontendRisk.selectedRisk} — ${frontendRisk.reason}`,
|
|
3131
3223
|
frontendRisk.forceFullGates
|
|
@@ -3141,13 +3233,15 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
3141
3233
|
: "Mock contract is blocked by deterministic generation-time Mock safety constraints.";
|
|
3142
3234
|
return buildBlockedFrontendMockDag(frontendSources, readOnlyPaths, forbiddenPaths, globalConstraints, blockedReason);
|
|
3143
3235
|
}
|
|
3144
|
-
//
|
|
3145
|
-
//
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3236
|
+
// Static verification is mandatory; existing or explicitly declared tests remain
|
|
3237
|
+
// required. Never use a no-op - but never freeze a command the project cannot run
|
|
3238
|
+
// either. See resolveFrontendStaticCommands for why `npm run build` is not the
|
|
3239
|
+
// only acceptable answer.
|
|
3240
|
+
const explicitFrontendCommands = buildExplicitFrontendVerifyCommands(sources.taskConfig, sources.repoRoot ?? process.cwd());
|
|
3241
|
+
const buildCommands = await resolveFrontendStaticCommands({
|
|
3242
|
+
repoRoot: sources.repoRoot ?? process.cwd(),
|
|
3243
|
+
explicitStatic: explicitFrontendCommands.staticCommands,
|
|
3244
|
+
});
|
|
3151
3245
|
const staticShellCommands = buildVerifyShellCommands({
|
|
3152
3246
|
repoRoot: sources.repoRoot ?? process.cwd(), commands: buildCommands, fallbackCommands: [],
|
|
3153
3247
|
});
|
|
@@ -454,6 +454,8 @@ export function parseDagRequestInterruptArgs(args) {
|
|
|
454
454
|
for (let i = 0; i < args.length; i += 1) {
|
|
455
455
|
const cursor = { value: i };
|
|
456
456
|
const arg = args[i];
|
|
457
|
+
if (arg === undefined)
|
|
458
|
+
break;
|
|
457
459
|
const runIdValue = readFlag(args, cursor, "run-id");
|
|
458
460
|
if (runIdValue !== undefined) {
|
|
459
461
|
runId = runIdValue;
|
|
@@ -684,6 +684,8 @@ export function parseDagStatusArgs(args) {
|
|
|
684
684
|
let runId;
|
|
685
685
|
for (let i = 0; i < args.length; i += 1) {
|
|
686
686
|
const arg = args[i];
|
|
687
|
+
if (arg === undefined)
|
|
688
|
+
break;
|
|
687
689
|
if (arg === "--run-id") {
|
|
688
690
|
runId = args[++i];
|
|
689
691
|
continue;
|
|
@@ -712,6 +714,8 @@ export function parseDagDoctorArgs(args) {
|
|
|
712
714
|
let markdown = false;
|
|
713
715
|
for (let i = 0; i < args.length; i += 1) {
|
|
714
716
|
const arg = args[i];
|
|
717
|
+
if (arg === undefined)
|
|
718
|
+
break;
|
|
715
719
|
if (arg === "--run-id") {
|
|
716
720
|
runId = args[++i];
|
|
717
721
|
if (!runId || runId.startsWith("-")) {
|
|
@@ -1075,6 +1075,8 @@ function validateRepairArtifactGateBeforeShell(input) {
|
|
|
1075
1075
|
if (!gate)
|
|
1076
1076
|
return;
|
|
1077
1077
|
const upstream = input.state.nodes[gate.fromNodeId];
|
|
1078
|
+
if (!upstream)
|
|
1079
|
+
return;
|
|
1078
1080
|
const parsed = parseSupervisorRepairArtifact(upstream);
|
|
1079
1081
|
if (!parsed.ok) {
|
|
1080
1082
|
throw new Error(`repair artifact gate failed: ${parsed.reason}`);
|
|
@@ -7,6 +7,8 @@ export function parseDagReconcileRunArgs(args) {
|
|
|
7
7
|
let reason;
|
|
8
8
|
for (let index = 0; index < args.length; index += 1) {
|
|
9
9
|
const arg = args[index];
|
|
10
|
+
if (arg === undefined)
|
|
11
|
+
break;
|
|
10
12
|
if (arg === "--run-id")
|
|
11
13
|
runId = args[++index];
|
|
12
14
|
else if (arg.startsWith("--run-id="))
|
|
@@ -330,7 +330,9 @@ export function resolveRepairTaskForGate(input) {
|
|
|
330
330
|
}
|
|
331
331
|
const candidates = directDownstream.filter(isGovernedRepairWriter);
|
|
332
332
|
if (candidates.length === 1) {
|
|
333
|
-
|
|
333
|
+
const [onlyCandidate] = candidates;
|
|
334
|
+
if (onlyCandidate)
|
|
335
|
+
return { ok: true, repairTask: onlyCandidate };
|
|
334
336
|
}
|
|
335
337
|
const candidateIds = directDownstream.map((task) => task.id).join(", ") || "none";
|
|
336
338
|
if (candidates.length === 0) {
|
|
@@ -449,6 +449,8 @@ export async function buildDagRunReportEntry(input) {
|
|
|
449
449
|
const rankNodes = input.state.ranks[rank] ?? [];
|
|
450
450
|
for (let order = 0; order < rankNodes.length; order += 1) {
|
|
451
451
|
const nodeId = rankNodes[order];
|
|
452
|
+
if (nodeId === undefined)
|
|
453
|
+
continue;
|
|
452
454
|
const node = input.state.nodes[nodeId];
|
|
453
455
|
if (!node)
|
|
454
456
|
continue;
|
|
@@ -26,6 +26,8 @@ export function parseDagRerunTaskArgs(args) {
|
|
|
26
26
|
let recoveryOperationId;
|
|
27
27
|
for (let index = 0; index < args.length; index += 1) {
|
|
28
28
|
const arg = args[index];
|
|
29
|
+
if (arg === undefined)
|
|
30
|
+
break;
|
|
29
31
|
if (arg === "--run-id")
|
|
30
32
|
parentRunId = args[++index];
|
|
31
33
|
else if (arg.startsWith("--run-id="))
|
|
@@ -192,6 +192,25 @@ export const dagJsonArtifactGateSchema = z.object({
|
|
|
192
192
|
const dagFrontendNodeIdSchema = z
|
|
193
193
|
.string()
|
|
194
194
|
.regex(/^[a-z][a-z0-9-]*$/, "frontend node id must be kebab-case");
|
|
195
|
+
/**
|
|
196
|
+
* Requirement id accepted in a generated DAG's `requiredRequirementIds`.
|
|
197
|
+
*
|
|
198
|
+
* These ids are lifted verbatim from the task requirement ledger, which mirrors
|
|
199
|
+
* whatever the PRD wrote. A PRD that writes `AC1` (no hyphen) therefore yields an
|
|
200
|
+
* id this schema rejects, and `dagSpecSchema.parse` surfaces zod's raw issue array
|
|
201
|
+
* - so the operator saw only `"message": "Invalid"` plus a JSON pointer and had no
|
|
202
|
+
* way to learn the constraint. The message below carries it into that error.
|
|
203
|
+
*
|
|
204
|
+
* This is deliberately a fail-closed check, not a normalizer: `AC1` is
|
|
205
|
+
* canonicalized to `AC-1` upstream by `normalizeAcceptanceId` in the semantic
|
|
206
|
+
* intake, so every workflow reads the same ids. What remains here is the guard
|
|
207
|
+
* for genuinely malformed input - and because `dagSpecSchema.parse` surfaces
|
|
208
|
+
* zod's raw issue array, the message below is what makes that guard
|
|
209
|
+
* self-explanatory instead of `"message": "Invalid"` plus a JSON pointer.
|
|
210
|
+
*/
|
|
211
|
+
const dagRequirementIdSchema = z
|
|
212
|
+
.string()
|
|
213
|
+
.regex(/^(?:REQ|BR|AC)-[A-Z0-9]+(?:-[A-Z0-9]+)*$/, 'requirement id must be "<REQ|BR|AC>-<SEGMENT>" with a hyphen after the prefix (e.g. AC-1, AC-FE-001, REQ-SRC-ABC123); ids come verbatim from the task requirement ledger, so a PRD writing "AC1" must write "AC-1"');
|
|
195
214
|
const openspecCandidatePathSchema = z
|
|
196
215
|
.string()
|
|
197
216
|
.refine((candidate) => !candidate.includes("\\") &&
|
|
@@ -210,9 +229,7 @@ export const dagFrontendPrewriteGateSchema = z.object({
|
|
|
210
229
|
planFallbackFromNodeIds: z.array(dagFrontendNodeIdSchema).default([]),
|
|
211
230
|
reviewFromNodeId: dagFrontendNodeIdSchema,
|
|
212
231
|
reviewFallbackFromNodeIds: z.array(dagFrontendNodeIdSchema).default([]),
|
|
213
|
-
requiredRequirementIds: z
|
|
214
|
-
.array(z.string().regex(/^(?:REQ|BR|AC)-[A-Z0-9]+(?:-[A-Z0-9]+)*$/))
|
|
215
|
-
.default([]),
|
|
232
|
+
requiredRequirementIds: z.array(dagRequirementIdSchema).default([]),
|
|
216
233
|
allowedMockStrategies: z
|
|
217
234
|
.array(z.enum(["native", "browser-intercept", "request-adapter", "not-needed"]))
|
|
218
235
|
.min(1),
|
|
@@ -422,9 +439,7 @@ export const dagFrontendDesignPolicySchema = z
|
|
|
422
439
|
.object({
|
|
423
440
|
schemaVersion: z.literal(1),
|
|
424
441
|
planFromNodeId: dagFrontendNodeIdSchema,
|
|
425
|
-
requiredRequirementIds: z
|
|
426
|
-
.array(z.string().regex(/^(?:REQ|BR|AC)-[A-Z0-9]+(?:-[A-Z0-9]+)*$/))
|
|
427
|
-
.default([]),
|
|
442
|
+
requiredRequirementIds: z.array(dagRequirementIdSchema).default([]),
|
|
428
443
|
allowedMockStrategies: z
|
|
429
444
|
.array(z.enum(["native", "browser-intercept", "request-adapter", "not-needed"]))
|
|
430
445
|
.min(1),
|
|
@@ -222,6 +222,8 @@ function validateRepairArtifactGateConfig(task, spec, issues) {
|
|
|
222
222
|
return;
|
|
223
223
|
}
|
|
224
224
|
const hardVerify = hardVerifyCandidates[0];
|
|
225
|
+
if (!hardVerify)
|
|
226
|
+
return;
|
|
225
227
|
const descendantIds = new Set();
|
|
226
228
|
const queue = [hardVerify.id];
|
|
227
229
|
while (queue.length > 0) {
|
|
@@ -411,6 +413,8 @@ function validateSameRankWriteSetConflicts(spec, ranks, issues) {
|
|
|
411
413
|
for (let j = i + 1; j < exclusiveWriters.length; j += 1) {
|
|
412
414
|
const left = exclusiveWriters[i];
|
|
413
415
|
const right = exclusiveWriters[j];
|
|
416
|
+
if (!left || !right)
|
|
417
|
+
continue;
|
|
414
418
|
const leftSet = left.writeSet ?? [];
|
|
415
419
|
const rightSet = right.writeSet ?? [];
|
|
416
420
|
for (const leftEntry of leftSet) {
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
- [`harness-methodology-tdd.md`](harness-methodology-tdd.md):测试先行的方法与边界。
|
|
11
11
|
- [`harness-methodology-verification.md`](harness-methodology-verification.md):如何用新鲜命令结果支持完成声明。
|
|
12
12
|
- [`harness-methodology-debugging.md`](harness-methodology-debugging.md):系统化定位故障,不用猜测代替证据。
|
|
13
|
+
- [`defect-ledger.md`](defect-ledger.md):真实缺陷台账与优先级规则——治理目标先从真故障选,再看代理指标排名。
|
|
13
14
|
- [`document-review-policy.md`](document-review-policy.md):文档 freshness、`reviewTier` 与 `lastReviewed` 更新边界。
|
|
14
15
|
- [`lightweight-github-collaboration.md`](lightweight-github-collaboration.md):短分支、简短 PR、CI 与 Squash Merge 的轻量协作规则。
|
|
15
16
|
- [`研发模式.md`](研发模式.md):Feature 团队推进、交付和知识回写方法论。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tea-agent/loop-agent",
|
|
3
|
-
"version": "0.43.0-next.
|
|
3
|
+
"version": "0.43.0-next.18",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"loop-agent": "bin/loop-agent.js",
|
|
@@ -84,6 +84,7 @@
|
|
|
84
84
|
"katex": "^0.16.47",
|
|
85
85
|
"mermaid": "^11.16.1",
|
|
86
86
|
"node-pty": "^1.2.0-beta.15",
|
|
87
|
+
"playwright-core": "^1.63.0",
|
|
87
88
|
"rehype-highlight": "^7.0.2",
|
|
88
89
|
"rehype-katex": "^7.0.1",
|
|
89
90
|
"remark-math": "^6.0.0",
|
|
@@ -103,6 +104,7 @@
|
|
|
103
104
|
"@types/react-dom": "^19.1.6",
|
|
104
105
|
"@types/semver": "^7.7.1",
|
|
105
106
|
"@vitest/coverage-v8": "^4.1.11",
|
|
107
|
+
"fast-check": "^4.10.0",
|
|
106
108
|
"react": "^19.1.0",
|
|
107
109
|
"react-dom": "^19.1.0",
|
|
108
110
|
"react-markdown": "^10.1.0",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{ag as o,ah as n}from"./mermaid.core-CQA4Nzmo.js";const t=(a,r)=>o.lang.round(n.parse(a)[r]);export{t as c};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{s as a,c as s,a as e,C as t}from"./chunk-GF5L2VYU-D9UA7hUb.js";import{_ as i}from"./mermaid.core-CQA4Nzmo.js";import"./chunk-5VM5RSS4-DGdvrNm8.js";import"./chunk-XXDRQBXY-CScR6mDD.js";import"./chunk-KBJHAD2P-BmuEOIh7.js";import"./chunk-2GRJ4B5K-CoviemSL.js";import"./index-wXlQohNs.js";var n={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{n as diagram};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{s as a,c as s,a as e,C as t}from"./chunk-GF5L2VYU-D9UA7hUb.js";import{_ as i}from"./mermaid.core-CQA4Nzmo.js";import"./chunk-5VM5RSS4-DGdvrNm8.js";import"./chunk-XXDRQBXY-CScR6mDD.js";import"./chunk-KBJHAD2P-BmuEOIh7.js";import"./chunk-2GRJ4B5K-CoviemSL.js";import"./index-wXlQohNs.js";var n={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{n as diagram};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{s as r,b as e,a,S as s}from"./chunk-5RXB4S5H-BsG2pmdQ.js";import{_ as i}from"./mermaid.core-CQA4Nzmo.js";import"./chunk-XXDRQBXY-CScR6mDD.js";import"./chunk-KBJHAD2P-BmuEOIh7.js";import"./chunk-2GRJ4B5K-CoviemSL.js";import"./index-wXlQohNs.js";var u={parser:a,get db(){return new s(2)},renderer:e,styles:r,init:i(t=>{t.state||(t.state={}),t.state.arrowMarkerAbsolute=t.arrowMarkerAbsolute},"init")};export{u as diagram};
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import{c as r,s as e}from"./flowDiagram-UKHOOZJN-BHIRvRr3.js";import{_ as a}from"./mermaid.core-CQA4Nzmo.js";import"./chunk-5VM5RSS4-DGdvrNm8.js";import"./chunk-XXDRQBXY-CScR6mDD.js";import"./chunk-KBJHAD2P-BmuEOIh7.js";import"./chunk-2GRJ4B5K-CoviemSL.js";import"./channel-BxdTlb_5.js";import"./index-wXlQohNs.js";var o=a(t=>`${e(t)}
|
|
2
|
-
.swimlane.cluster rect {
|
|
3
|
-
stroke: ${t.clusterBorder} !important;
|
|
4
|
-
}
|
|
5
|
-
[data-look="neo"].cluster rect {
|
|
6
|
-
filter: none;
|
|
7
|
-
}
|
|
8
|
-
`,"getStyles"),s=o,n=r({defaultLayout:"swimlane",styles:s});export{n as diagram};
|