@tea-agent/loop-agent 0.43.0-next.18 → 0.43.0-next.19
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 +60 -2308
- package/README.md +7 -0
- package/dist/application/task-lifecycle/advance.js +38 -7
- package/dist/build-stamp.json +3 -3
- package/dist/commands/init.js +13 -9
- package/dist/commands/task-advance.js +32 -0
- package/dist/executors/dag-pi-executor.js +1615 -188
- package/dist/executors/pi-executor.js +85 -3
- package/dist/executors/pi-sdk-executor.js +18 -0
- package/dist/executors/shell-executor.js +92 -7
- package/dist/shared/backend-dogfood-preflight.js +47 -0
- package/dist/shared/dag-failure-category.js +3 -0
- package/dist/shared/operator/capabilities.js +180 -0
- package/dist/task/config-types.js +29 -0
- package/dist/task/contract/project.js +9 -0
- package/dist/task/contract/schema.js +17 -0
- package/dist/task/source-prepare/build-draft.js +60 -0
- package/dist/worker/console/chat/pi-mode-loop-isolation.js +155 -0
- package/dist/worker/console/chat/pi-runtime/custom-tools/operator-tools.js +24 -16
- package/dist/worker/console/chat/pi-runtime/custom-tools/scheduled-goal-tools.js +22 -0
- package/dist/worker/console/chat/pi-runtime/progressive-tools.js +195 -0
- package/dist/worker/console/chat/pi-runtime.js +86 -27
- package/dist/worker/console/chat/routes.js +19 -0
- package/dist/worker/console/chat/scheduled-goal-booking.js +59 -0
- package/dist/worker/console/chat/scheduled-goal-delivery.js +27 -0
- package/dist/worker/console/chat/scheduled-goal-request.js +190 -0
- package/dist/worker/console/chat/session-mode.js +7 -3
- package/dist/worker/console/chat/session-store.js +42 -9
- package/dist/worker/console/chat/tool-preview.js +115 -0
- package/dist/worker/console/chat/turn-order.js +13 -0
- package/dist/worker/console/chat/turn-process.js +32 -30
- package/dist/worker/console/operator-actions.js +50 -0
- package/dist/worker/console/prd-intake-bridge.js +54 -1
- package/dist/worker/console/scheduled-goal-host.js +98 -0
- package/dist/worker/console/scheduled-goal-operation-adapter.js +127 -0
- package/dist/worker/console/server.js +15 -0
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-B_UDknhR.js → abnfDiagram-N423BO3Z-8-j6y-sd.js} +1 -1
- package/dist/worker/console/static/assets/{arc-D6hU0drN.js → arc-eoQiMvuk.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-DmVikdRK.js → architectureDiagram-T3A2C74G-C4A3uMcI.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-DydABCRg.js → blockDiagram-VBNYF7ZC-D4zD2F-Q.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-DrXKC2Jl.js → c4Diagram-5PPSVZJV-j1RkJziL.js} +1 -1
- package/dist/worker/console/static/assets/channel-ChE7y-cx.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-D2-qFxr7.js → chunk-2GRJ4B5K-YBHmhik1.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-ChfeF41P.js → chunk-2Q5K7J3B-COfWyo9P.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-Drwrz55x.js → chunk-5RXB4S5H-I99OUkHY.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-CAQGErL7.js → chunk-5VM5RSS4-XKxoJNJ4.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DHO38w5U.js → chunk-6Q2QTUOP-DLT_cYx1.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-EYIN0EOI.js → chunk-GF5L2VYU-CxcKZ9mV.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-CrFLKqkT.js → chunk-JWPE2WC7-V1EqXdjY.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DMchffoo.js → chunk-KBJHAD2P-DebTtdFp.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DaSvtKQa.js → chunk-RYQCIY6F-B-ivNRDf.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-DcW8W9Qj.js → chunk-XXDRQBXY-DC_Ds11b.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C01TCf2X.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C01TCf2X.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BUirrMEi.js → cose-bilkent-JH36ORCC-sWEqKwIb.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BXzO8iXR.js → cynefin-VYW2F7L2-BXa_dcu4.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-DAtgaxIn.js → cynefinDiagram-MW4NZA55-C-Mle84F.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-C1re9Noh.js → dagre-VZM6K2ZE-CXPDBITe.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-N8Kj08J9.js → diagram-7IWD3JNH-CC-WJQfa.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-D6BpSLs3.js → diagram-B4RE2ZJO-CDAV5Vs4.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-C2Vlgnl4.js → diagram-LBJQPF4R-CmFiAcNz.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-CMbqtVLS.js → diagram-Q27KOJAE-DPBZHuyn.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-A770Eb-0.js → diagram-UB23O5K3-jUlm_Ds3.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-LV2w_2pT.js → ebnfDiagram-BXEA7PRR-DWhQ3mfY.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-bwqf56ah.js → erDiagram-JOGREHBK-Tr2gMqet.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-BnVtHhZh.js → flowDiagram-UKHOOZJN-DJjVQHPA.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-BGVToEqc.js → ganttDiagram-PKOTCBZU-D74dQ4u0.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-DL2l7Vne.js → gitGraphDiagram-DS77QQ5N-DwW0tZ0X.js} +1 -1
- package/dist/worker/console/static/assets/index-BWkIfcrK.css +1 -0
- package/dist/worker/console/static/assets/{index-B_V4wvXs.js → index-Cdkvw_H6.js} +97 -97
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-BWIpubOx.js → infoDiagram-6WML65LV-ILCbxJyb.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-DjYBD8vv.js → ishikawaDiagram-WSZJBQD7-DeuWBQ89.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-C0_mBaHb.js → journeyDiagram-NVQOT4AX-CF5ih8Fk.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-Cy71zbX6.js → kanban-definition-27J2QSJJ-C7yOSuRO.js} +1 -1
- package/dist/worker/console/static/assets/{linear-Dt3_w3Vn.js → linear-BDZ9riWi.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-DnNOlfEP.js → mermaid.core-7pKqYtpZ.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-DAbTspwq.js → mindmap-definition-FAOFIHXS-LeJDybSU.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-NNiM141p.js → pegDiagram-VL7TDLO6-BJvT3pMD.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-CN80Z1Do.js → pieDiagram-7S7Q4E2Y-_rGqpMan.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-Bxyfh0JU.js → quadrantDiagram-CIZ2JOQS-DPcSv5aA.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BlWnC79M.js → railroadDiagram-AXF67PYL-Drxx4hkJ.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-BrEu4P_e.js → requirementDiagram-LRYGKXZP-BCTUdU4z.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-Bt70igBr.js → sankeyDiagram-W5VNT64P-B6wzbZmB.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-C8DQR-r9.js → sequenceDiagram-SI44F4Z6-BGt8d3QQ.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-DEMinJAT.js → sizeCapture-X5ZJPWSS-7nlhJKo7.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-DDdDBzQc.js → stateDiagram-OKZ733FA-Cv2stqMA.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DC7V5vcq.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-G2wcBs3T.js → swimlanes-SLNWSIFB-qDAo4Yc1.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Bwy4QUTO.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-Dzn4g2gJ.js → timeline-definition-Z64GVDOM-CKbDNKTr.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-5v2rP9oO.js → vennDiagram-T6HMQDX7-DI-9EHic.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-B4EOx7ew.js → wardleyDiagram-T6FBY63Y-BrzzRDpX.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CQ1v8eYP.js → xychartDiagram-ELKLHX3M-BclH5hGh.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +41 -11
- package/dist/worker/console/static-src/operator-chat/compaction-message.js +3 -17
- package/dist/worker/console/static-src/operator-chat/timeline-merge.js +29 -0
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +2 -5
- package/dist/worker/console/workspace-context.js +11 -0
- package/dist/worker/observe/static/operator-chrome.js +3 -1
- package/dist/worker/scheduler/scheduled-goal-dispatch.js +117 -0
- package/dist/worker/scheduler/scheduled-goal-evidence.js +167 -0
- package/dist/worker/scheduler/scheduled-goal-recovery.js +62 -0
- package/dist/worker/scheduler/scheduled-goal-store.js +699 -0
- package/dist/worker/scheduler/scheduled-goal-supervisor.js +132 -0
- package/dist/worker/scheduler/scheduled-goal-time.js +102 -0
- package/dist/worker/scheduler/scheduled-goal-types.js +95 -0
- package/dist/workflows/dag/backend-test-markdown-workflow.js +6 -24
- package/dist/workflows/dag/backend-test-plan-protocol.js +82 -5
- package/dist/workflows/dag/dag-retry-schema.js +11 -0
- package/dist/workflows/dag/frontend-closeout.js +4 -2
- package/dist/workflows/dag/frontend-committed-facts.js +461 -0
- package/dist/workflows/dag/frontend-durable-tools.js +15 -3
- package/dist/workflows/dag/frontend-implementation-contract.js +365 -8
- package/dist/workflows/dag/frontend-plan-canary.js +53 -0
- package/dist/workflows/dag/frontend-plan-decision-contract.js +803 -0
- package/dist/workflows/dag/frontend-plan-render.js +0 -2
- package/dist/workflows/dag/frontend-prewrite-gate.js +3 -3
- package/dist/workflows/dag/frontend-provider-capability-matrix.js +8 -61
- package/dist/workflows/dag/frontend-recovery-run.js +57 -0
- package/dist/workflows/dag/frontend-review-context.js +43 -70
- package/dist/workflows/dag/frontend-risk.js +92 -10
- package/dist/workflows/dag/frontend-session-budget.js +117 -3
- package/dist/workflows/dag/frontend-shape.js +11 -55
- package/dist/workflows/dag/frontend-test-execution-evidence.js +35 -10
- package/dist/workflows/dag/frontend-typed-event-store.js +32 -25
- package/dist/workflows/dag/frontend-verification-trace.js +11 -2
- package/dist/workflows/dag/frontend-writer-admission.js +3 -47
- package/dist/workflows/dag/frontend-writer-status.js +0 -23
- package/dist/workflows/dag/init-hybrid.js +182 -185
- package/dist/workflows/dag/lifecycle.js +7 -2
- package/dist/workflows/dag/node-execution.js +48 -13
- package/dist/workflows/dag/rerun-plan.js +10 -0
- package/dist/workflows/dag/rerun-task.js +77 -1
- package/dist/workflows/dag/retry-policy.js +18 -0
- package/dist/workflows/dag/scheduler.js +2 -4
- package/dist/workflows/dag/types.js +23 -0
- package/docs/README.md +1 -0
- package/docs/init-surface.manifest.json +1 -0
- package/docs/operations/README.md +2 -0
- package/docs/templates/README.md +1 -1
- package/docs/templates/agent-dag.schema.json +2 -2
- package/docs/templates/backend-test-dag.json +14 -10
- package/docs/templates/frontend-implementation-contract.schema.json +0 -7
- package/docs/templates/frontend-implementation-dag.json +8 -9
- package/package.json +6 -3
- package/skills/frontend-bounded-implement/SKILL.md +3 -4
- package/skills/frontend-design-review/SKILL.md +7 -12
- package/skills/frontend-design-review/references/review-checklist.md +7 -7
- package/skills/frontend-plan/SKILL.md +8 -18
- package/skills/frontend-plan/references/decision-contract.md +19 -26
- package/skills/frontend-review/SKILL.md +21 -25
- package/skills/frontend-review/references/review-findings.md +48 -24
- package/dist/worker/console/static/assets/channel-Drecd94a.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-8Udu0t8-.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-8Udu0t8-.js +0 -1
- package/dist/worker/console/static/assets/index-DcudonhZ.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-CyCiS0Lt.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-U0lhi6N0.js +0 -8
- package/dist/workflows/dag/frontend-shadow-dual-write.js +0 -975
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { createHash } from "node:crypto";
|
|
11
11
|
import { z } from "zod";
|
|
12
|
+
import { collectFrontendSharedSignals, isNarrowProductSurface } from "./frontend-risk.js";
|
|
12
13
|
/** Fixture-only node counts. They are NOT the authoritative 15/13/11
|
|
13
14
|
* topology: split-required has no count. */
|
|
14
15
|
export const FRONTEND_SHAPE_NODE_COUNTS = {
|
|
@@ -17,16 +18,6 @@ export const FRONTEND_SHAPE_NODE_COUNTS = {
|
|
|
17
18
|
standard: 11,
|
|
18
19
|
high: 11,
|
|
19
20
|
};
|
|
20
|
-
const SHAPE_ESCALATION_PATTERNS = [
|
|
21
|
-
{ id: "auth-permission", re: /\bauth\b|\bpermission\b|\brbac\b|登录|权限|\bjwt\b|\bsession\b/i },
|
|
22
|
-
{ id: "sensitive-action", re: /\bpayment\b|delete\s+account|支付|删除用户|\bdestructive\b/i },
|
|
23
|
-
{ id: "ssr-boundary", re: /\bssr\b|\brsc\b|server\s+component|\bhydration\b|server-client/i },
|
|
24
|
-
{ id: "new-route", re: /new\s+route|新增路由|createBrowserRouter|add\s+route/i },
|
|
25
|
-
{ id: "shared-public-api", re: /public\s+api|shared\s+component|公共组件|破坏性|breaking\s+change/i },
|
|
26
|
-
{ id: "schema-api-change", re: /\bopenapi\b|graphql\s+schema|api\s+contract|接口变更|schema\s+change/i },
|
|
27
|
-
{ id: "new-dependency", re: /new\s+dependency|add\s+dependency|npm\s+install|新增依赖/i },
|
|
28
|
-
{ id: "cross-domain-paths", re: /多个领域|cross-module|multiple\s+packages/i },
|
|
29
|
-
];
|
|
30
21
|
const SHAPE_SMALL_PATTERNS = [
|
|
31
22
|
{ id: "single-component", re: /single\s+component|单一组件|局部样式|style\s+tweak|css\s+only|文案|copy\s+change/i },
|
|
32
23
|
{ id: "no-remote", re: /no\s+api|无\s*api|无\s*接口|本地状态|local\s+state\s+only|pure\s+ui/i },
|
|
@@ -59,48 +50,13 @@ function pathSpreadScore(paths) {
|
|
|
59
50
|
return tops.size;
|
|
60
51
|
}
|
|
61
52
|
function collectEscalationSignals(blob, supervised) {
|
|
62
|
-
const signals =
|
|
63
|
-
for (const pattern of SHAPE_ESCALATION_PATTERNS) {
|
|
64
|
-
if (hasAffirmativeShapeSignal(blob, pattern))
|
|
65
|
-
signals.push(pattern.id);
|
|
66
|
-
}
|
|
67
|
-
if (supervised)
|
|
68
|
-
signals.push("supervised-profile");
|
|
53
|
+
const signals = collectFrontendSharedSignals(blob, supervised);
|
|
69
54
|
if (SHAPE_SPLIT_REQUIRED_PATTERN.test(blob) &&
|
|
70
55
|
!SHAPE_SPLIT_REQUIRED_NEGATION.test(blob)) {
|
|
71
56
|
signals.push("large-requirement-split");
|
|
72
57
|
}
|
|
73
58
|
return [...new Set(signals)];
|
|
74
59
|
}
|
|
75
|
-
function isExplicitShapeExclusion(clause, pattern) {
|
|
76
|
-
const match = new RegExp(pattern.re.source, "i").exec(clause);
|
|
77
|
-
if (!match)
|
|
78
|
-
return false;
|
|
79
|
-
const prefix = clause.slice(0, match.index).trim();
|
|
80
|
-
const suffix = clause.slice(match.index + match[0].length).trim();
|
|
81
|
-
const actionInSignal = ["new-route", "new-dependency"].includes(pattern.id);
|
|
82
|
-
if (suffix && !(actionInSignal && /^no$/i.test(prefix) && /^is\s+(?:required|needed)$/i.test(suffix)))
|
|
83
|
-
return false;
|
|
84
|
-
if (/^(?:不涉及|不(?:修改|变更|调整|改变|新增|添加)|无需(?:修改|变更|调整|改变|新增|添加)|no\s+changes?\s+to|(?:do\s+not|don't)\s+(?:change|modify))$/i.test(prefix))
|
|
85
|
-
return true;
|
|
86
|
-
return actionInSignal && /^(?:不|禁止|无需|无须|不需要|no|without|do\s+not|don't)$/i.test(prefix);
|
|
87
|
-
}
|
|
88
|
-
/** Only a complete, independent scope exclusion can suppress a risk signal.
|
|
89
|
-
* Nested negation, conditions and trailing consequences remain risk evidence;
|
|
90
|
-
* an excluded occurrence never cancels another occurrence elsewhere. */
|
|
91
|
-
function hasAffirmativeShapeSignal(blob, pattern) {
|
|
92
|
-
for (const sentence of blob.split(/[\n。.;;!?!?]/)) {
|
|
93
|
-
if (!pattern.re.test(sentence))
|
|
94
|
-
continue;
|
|
95
|
-
// Commas can attach a condition to an exclusion. A comma-separated list
|
|
96
|
-
// is independent only when every item is itself an explicit exclusion.
|
|
97
|
-
const clauses = sentence.split(/[,,、]/).map(part => part.trim().replace(/^(?:[-*+]|\d+\))\s+/, ""));
|
|
98
|
-
if (clauses.every(clause => SHAPE_ESCALATION_PATTERNS.some(candidate => isExplicitShapeExclusion(clause, candidate))))
|
|
99
|
-
continue;
|
|
100
|
-
return true;
|
|
101
|
-
}
|
|
102
|
-
return false;
|
|
103
|
-
}
|
|
104
60
|
/** Negation applies to the API mention itself, never to unrelated Mock policy. */
|
|
105
61
|
function hasFrontendRemoteSignal(blob) {
|
|
106
62
|
const remaining = blob
|
|
@@ -126,7 +82,7 @@ function resolveBaselineFrontendTaskShape(input) {
|
|
|
126
82
|
}
|
|
127
83
|
const paths = input.allowedPaths ?? [];
|
|
128
84
|
const spread = pathSpreadScore(paths);
|
|
129
|
-
const concentrated = spread <= 1 && paths.length <= 4;
|
|
85
|
+
const concentrated = paths.length > 0 && spread <= 1 && paths.length <= 4;
|
|
130
86
|
const hasApiSignal = hasFrontendRemoteSignal(blob);
|
|
131
87
|
const complexity = (input.complexity ?? "").toLowerCase();
|
|
132
88
|
// split-required wins over everything: it is a termination/escalation
|
|
@@ -172,17 +128,17 @@ function resolveBaselineFrontendTaskShape(input) {
|
|
|
172
128
|
reason: "micro scope: trivial single-file change without remote API",
|
|
173
129
|
};
|
|
174
130
|
}
|
|
175
|
-
// small: no escalation, no supervised,
|
|
176
|
-
// no remote API signal.
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
131
|
+
// small: no escalation, no supervised, narrow single-domain delivery
|
|
132
|
+
// surface, no remote API signal, and not an explicitly large task. Keyword
|
|
133
|
+
// and explicit "small/low" complexity hints are no longer required — they
|
|
134
|
+
// were a fragile proxy for "this is genuinely a small change" and blocked
|
|
135
|
+
// genuinely-small tasks whose authors used different wording.
|
|
136
|
+
if (isNarrowProductSurface(paths) && !hasApiSignal && complexity !== "large") {
|
|
181
137
|
return {
|
|
182
138
|
shape: "small",
|
|
183
|
-
signals: smallHits,
|
|
139
|
+
signals: smallHits.length > 0 ? smallHits : ["structured-small"],
|
|
184
140
|
rejectedSignals: [],
|
|
185
|
-
reason:
|
|
141
|
+
reason: "narrow single-domain delivery surface without remote API or escalation; small topology",
|
|
186
142
|
};
|
|
187
143
|
}
|
|
188
144
|
const rejected = smallHits.map((id) => `small:${id}:blocked-by-${hasApiSignal ? "api-signal" : "path-spread"}`);
|
|
@@ -1,21 +1,18 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
3
|
import { z } from "zod";
|
|
4
|
-
/**
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
return undefined;
|
|
10
|
-
const direct = /^(?:(?:npx|pnpm exec|yarn exec)\s+)?vitest\s+run(?:\s|$)/.test(input.command.trim());
|
|
11
|
-
const npm = /^npm\s+(?:run\s+([\w:-]+)|(test))(?=\s|$)(.*)$/.exec(input.command.trim());
|
|
4
|
+
/** Decide whether a bare vitest/npm-test command is observable and, if so,
|
|
5
|
+
* return the command with the separator (" --") a reporter would need. */
|
|
6
|
+
async function adaptPlainTestCommand(command, cwd) {
|
|
7
|
+
const direct = /^(?:(?:npx|pnpm exec|yarn exec)\s+)?vitest\s+run(?:\s|$)/.test(command.trim());
|
|
8
|
+
const npm = /^npm\s+(?:run\s+([\w:-]+)|(test))(?=\s|$)(.*)$/.exec(command.trim());
|
|
12
9
|
let separator = "";
|
|
13
10
|
if (!direct) {
|
|
14
11
|
if (!npm)
|
|
15
12
|
return undefined;
|
|
16
13
|
let manifest;
|
|
17
14
|
try {
|
|
18
|
-
manifest = JSON.parse(await readFile(path.join(
|
|
15
|
+
manifest = JSON.parse(await readFile(path.join(cwd, "package.json"), "utf8"));
|
|
19
16
|
}
|
|
20
17
|
catch {
|
|
21
18
|
return undefined;
|
|
@@ -27,8 +24,36 @@ export async function withFrontendTestReporter(input) {
|
|
|
27
24
|
return undefined;
|
|
28
25
|
separator = npm[3]?.trim() ? "" : " --";
|
|
29
26
|
}
|
|
27
|
+
return `${command}${separator}`;
|
|
28
|
+
}
|
|
29
|
+
/** Observe supported frozen Vitest commands without changing their selection. */
|
|
30
|
+
export async function withFrontendTestReporter(input) {
|
|
31
|
+
// Compound commands/wrappers need their own adapter: never append flags to
|
|
32
|
+
// a different shell command, change selection, or override a user reporter.
|
|
33
|
+
if (/[;&|<>`\n\r$]/.test(input.command) || /--(?:reporter|outputFile)/.test(input.command))
|
|
34
|
+
return undefined;
|
|
35
|
+
// Frozen verify commands are env-prefixed bash wrappers —
|
|
36
|
+
// `HARNESS_ALLOW_ACTIVE_DAG_RUNS=1 'bash' '-lc' 'npm test'` — so the JSON
|
|
37
|
+
// reporter must be injected INSIDE the quoted payload; appending it to the
|
|
38
|
+
// wrapper would target bash's own CLI and produce no test report at all
|
|
39
|
+
// (smoke r54: every behavior verification target then failed the trace
|
|
40
|
+
// gate with "test execution report unavailable").
|
|
41
|
+
const bashWrapped = /^((?:[A-Za-z_][A-Za-z0-9_]*=(?:'[^']*'|[^\s'"]+)\s+)*)'bash'\s+'-lc'\s+'([^']*)'\s*$/.exec(input.command);
|
|
42
|
+
if (bashWrapped) {
|
|
43
|
+
const inner = bashWrapped[2];
|
|
44
|
+
if (/[;&|<>`\n\r$]/.test(inner) || /--(?:reporter|outputFile)/.test(inner))
|
|
45
|
+
return undefined;
|
|
46
|
+
const adaptedInner = await adaptPlainTestCommand(inner, input.cwd);
|
|
47
|
+
if (!adaptedInner)
|
|
48
|
+
return undefined;
|
|
49
|
+
const quote = (value) => `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
50
|
+
return input.command.replace(`'${inner}'`, `'${adaptedInner} --reporter=json --outputFile=${quote(input.reportPath)}'`);
|
|
51
|
+
}
|
|
52
|
+
const adapted = await adaptPlainTestCommand(input.command, input.cwd);
|
|
53
|
+
if (!adapted)
|
|
54
|
+
return undefined;
|
|
30
55
|
const quote = (value) => `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
31
|
-
return `${
|
|
56
|
+
return `${adapted} --reporter=json --outputFile=${quote(input.reportPath)}`;
|
|
32
57
|
}
|
|
33
58
|
const resultSchema = z.object({
|
|
34
59
|
testResults: z.array(z.object({
|
|
@@ -31,18 +31,11 @@ export const TYPED_EVENT_FACT_KINDS = [
|
|
|
31
31
|
"provider-ended-without-terminal",
|
|
32
32
|
"read-only-fact-channel",
|
|
33
33
|
];
|
|
34
|
-
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
|
|
39
|
-
*
|
|
40
|
-
* Kept deliberately separate from `TYPED_EVENT_FACT_KINDS`: widening the shadow
|
|
41
|
-
* catalog must never widen the provider capability matrix (which stays frozen
|
|
42
|
-
* at its seven kinds). `target-surface` is shared by the scout and plan
|
|
43
|
-
* origins, so the union of the two catalogs is 12 unique kinds.
|
|
44
|
-
*/
|
|
45
|
-
export const SHADOW_FACT_KINDS = [
|
|
34
|
+
/** Frontend DAG decision facts. These are committed runtime facts, not a
|
|
35
|
+
* secondary text channel; Policy, Admission and Plan compilation read them
|
|
36
|
+
* directly. Kept separate from provider capability facts so the provider
|
|
37
|
+
* matrix remains frozen. */
|
|
38
|
+
export const FRONTEND_DAG_FACT_KINDS = [
|
|
46
39
|
"requirement",
|
|
47
40
|
"constraint",
|
|
48
41
|
"open-question",
|
|
@@ -56,11 +49,26 @@ export const SHADOW_FACT_KINDS = [
|
|
|
56
49
|
"design-deviation",
|
|
57
50
|
"dependency",
|
|
58
51
|
];
|
|
52
|
+
/** Experimental decision Plan fact catalog. The decision tools emit semantic
|
|
53
|
+
* decisions (module placement, reuse, state ownership, Mock boundary, behavior
|
|
54
|
+
* verification focus); `data-flow` and `dependency` reuse the DAG catalog above.
|
|
55
|
+
* Kept as a dedicated catalog so widening the decision surface never widens the
|
|
56
|
+
* frozen provider / event / review / design catalogs. */
|
|
57
|
+
export const FRONTEND_DECISION_FACT_KINDS = [
|
|
58
|
+
"module-placement",
|
|
59
|
+
"reuse-decision",
|
|
60
|
+
"state-ownership",
|
|
61
|
+
"api-mock-boundary",
|
|
62
|
+
"verification-focus",
|
|
63
|
+
];
|
|
64
|
+
/** Terminal fact for the experimental decision Plan path. Kept separate from the
|
|
65
|
+
* decision fact catalog so it never gets collected as a decision entry. */
|
|
66
|
+
export const DECISION_TERMINAL_FACT_KINDS = ["finalize_decision"];
|
|
59
67
|
/**
|
|
60
68
|
* M5 review terminal fact catalog (AC-001). These two committed typed tools are
|
|
61
69
|
* the authoritative terminal verdict of `frontend-review-pi`. They are kept in
|
|
62
70
|
* a dedicated catalog so widening the review terminal surface never widens the
|
|
63
|
-
* frozen
|
|
71
|
+
* frozen provider/event catalogs.
|
|
64
72
|
*/
|
|
65
73
|
export const REVIEW_TERMINAL_FACT_KINDS = [
|
|
66
74
|
"approve_review",
|
|
@@ -72,7 +80,7 @@ export const REVIEW_TERMINAL_FACT_KINDS = [
|
|
|
72
80
|
* `approve_design` / `request_design_changes` instead of the legacy first-line
|
|
73
81
|
* `VERDICT: pass|request-revision` text protocol. Kept in a dedicated catalog
|
|
74
82
|
* so widening the design terminal surface never widens the frozen provider /
|
|
75
|
-
*
|
|
83
|
+
* event / review catalogs.
|
|
76
84
|
*/
|
|
77
85
|
export const DESIGN_TERMINAL_FACT_KINDS = [
|
|
78
86
|
"approve_design",
|
|
@@ -84,7 +92,7 @@ export const DESIGN_TERMINAL_FACT_KINDS = [
|
|
|
84
92
|
* open-question/split-proposal facts plus the `contract-finalized` terminal
|
|
85
93
|
* fact carrying the terminal disposition (`ready | ready-with-assumptions |
|
|
86
94
|
* blocked` + optional `blockingOwner`). Kept in a dedicated catalog so
|
|
87
|
-
* widening the contract surface never widens the frozen provider /
|
|
95
|
+
* widening the contract surface never widens the frozen provider /
|
|
88
96
|
* event / review / design catalogs.
|
|
89
97
|
*/
|
|
90
98
|
export const CONTRACT_FACT_KINDS = [
|
|
@@ -142,8 +150,8 @@ export const FRONTEND_ISSUE_CATEGORY_VALUES = [
|
|
|
142
150
|
];
|
|
143
151
|
export const frontendIssueCategorySchema = z.enum(FRONTEND_ISSUE_CATEGORY_VALUES);
|
|
144
152
|
/** Runtime control facts committed at frontend shape boundaries. Kept outside
|
|
145
|
-
* the frozen provider
|
|
146
|
-
*
|
|
153
|
+
* the frozen provider catalog so adding control authority cannot widen that
|
|
154
|
+
* capability surface. */
|
|
147
155
|
export const FRONTEND_SHAPE_FACT_KINDS = ["frontend-shape"];
|
|
148
156
|
export const typedEventPhaseSchema = z.enum([
|
|
149
157
|
"staged",
|
|
@@ -153,7 +161,9 @@ export const typedEventPhaseSchema = z.enum([
|
|
|
153
161
|
const ALL_TYPED_EVENT_FACT_KIND_VALUES = [
|
|
154
162
|
...new Set([
|
|
155
163
|
...TYPED_EVENT_FACT_KINDS,
|
|
156
|
-
...
|
|
164
|
+
...FRONTEND_DAG_FACT_KINDS,
|
|
165
|
+
...FRONTEND_DECISION_FACT_KINDS,
|
|
166
|
+
...DECISION_TERMINAL_FACT_KINDS,
|
|
157
167
|
...REVIEW_TERMINAL_FACT_KINDS,
|
|
158
168
|
...DESIGN_TERMINAL_FACT_KINDS,
|
|
159
169
|
...FRONTEND_SHAPE_FACT_KINDS,
|
|
@@ -166,7 +176,7 @@ const ALL_TYPED_EVENT_FACT_KIND_VALUES = [
|
|
|
166
176
|
export const typedEventFactKindSchema = z.enum(ALL_TYPED_EVENT_FACT_KIND_VALUES);
|
|
167
177
|
/** A typed event fact is a `kind`-discriminated record. The schema stays
|
|
168
178
|
* permissive beyond `kind` (arbitrary JSON-safe payload) so provider/model
|
|
169
|
-
*
|
|
179
|
+
* provider facts can carry their own fields, but `kind` is always validated. */
|
|
170
180
|
export const typedEventFactSchema = z
|
|
171
181
|
.record(z.unknown())
|
|
172
182
|
.superRefine((value, ctx) => {
|
|
@@ -178,8 +188,7 @@ export const typedEventFactSchema = z
|
|
|
178
188
|
});
|
|
179
189
|
}
|
|
180
190
|
});
|
|
181
|
-
/** A single review finding shared by both typed terminal verdicts.
|
|
182
|
-
* legacy JSON review finding shape so typed↔JSON equivalence stays total. */
|
|
191
|
+
/** A single review finding shared by both typed terminal verdicts. */
|
|
183
192
|
export const reviewFindingSchema = z
|
|
184
193
|
.object({
|
|
185
194
|
severity: z.enum(["Critical", "Important", "Minor", "Info"]),
|
|
@@ -190,8 +199,7 @@ export const reviewFindingSchema = z
|
|
|
190
199
|
})
|
|
191
200
|
.strict();
|
|
192
201
|
/** `approve_review` terminal fact payload. Approval may carry informational
|
|
193
|
-
* findings (Minor/Info) but never Critical/Important
|
|
194
|
-
* JSON verdict applies to `verdict: "pass"`. */
|
|
202
|
+
* findings (Minor/Info) but never Critical/Important. */
|
|
195
203
|
export const approveReviewFactSchema = z
|
|
196
204
|
.object({
|
|
197
205
|
kind: z.literal("approve_review"),
|
|
@@ -221,8 +229,7 @@ export const requestReviewChangesFactSchema = z
|
|
|
221
229
|
})
|
|
222
230
|
.strict();
|
|
223
231
|
/** `approve_design` terminal fact payload. Approval may carry informational
|
|
224
|
-
* findings (Minor/Info) but never Critical/Important
|
|
225
|
-
* text protocol applies to `VERDICT: pass`. */
|
|
232
|
+
* findings (Minor/Info) but never Critical/Important. */
|
|
226
233
|
export const approveDesignFactSchema = z
|
|
227
234
|
.object({
|
|
228
235
|
kind: z.literal("approve_design"),
|
|
@@ -260,6 +260,8 @@ export async function evaluateFrontendWriterTracePreflight(input) {
|
|
|
260
260
|
file: target.file,
|
|
261
261
|
...(target.symbol ? { symbol: target.symbol } : {}),
|
|
262
262
|
status: fileTrace.issues.length ? "failed" : "ok",
|
|
263
|
+
evidence: fileTrace.issues.length ? "unavailable" : "file-exists",
|
|
264
|
+
behaviorCoverage: "unconfirmed",
|
|
263
265
|
matchedNodeIds: [],
|
|
264
266
|
issues: fileTrace.issues,
|
|
265
267
|
});
|
|
@@ -296,7 +298,10 @@ export async function evaluateVerificationTargets(input) {
|
|
|
296
298
|
});
|
|
297
299
|
issues.push(...fileTrace.issues);
|
|
298
300
|
const executed = input.executedTests?.get(target.commandLabel);
|
|
299
|
-
if (target.mode !== "static" && executed
|
|
301
|
+
if (target.mode !== "static" && executed === undefined) {
|
|
302
|
+
issues.push(`test execution report unavailable for behavior target: ${target.id}`);
|
|
303
|
+
}
|
|
304
|
+
else if (target.mode !== "static" && executed !== undefined) {
|
|
300
305
|
// safePath permits "./" prefixes; reporter files are cwd-relative.
|
|
301
306
|
const targetFile = path.posix.normalize(target.file.replace(/\\/g, "/"));
|
|
302
307
|
const matching = executed.filter((test) => test.file === targetFile);
|
|
@@ -314,6 +319,10 @@ export async function evaluateVerificationTargets(input) {
|
|
|
314
319
|
file: target.file,
|
|
315
320
|
symbol: target.symbol,
|
|
316
321
|
status,
|
|
322
|
+
evidence: issues.length
|
|
323
|
+
? "unavailable"
|
|
324
|
+
: target.mode === "static" ? "command-execution" : "test-file-execution",
|
|
325
|
+
behaviorCoverage: "unconfirmed",
|
|
317
326
|
matchedNodeIds: owners ?? [],
|
|
318
327
|
issues,
|
|
319
328
|
});
|
|
@@ -520,7 +529,7 @@ export function formatFrontendVerificationTraceStdout(result) {
|
|
|
520
529
|
`Browser: ${result.browserStatus}`,
|
|
521
530
|
`Visual: ${result.visualStatus}`,
|
|
522
531
|
`Targets: ${result.targets.length}`,
|
|
523
|
-
...result.targets.map((target) => `- ${target.id}:
|
|
532
|
+
...result.targets.map((target) => `- ${target.id}: execution=${target.status} evidence=${target.evidence} behavior-coverage=${target.behaviorCoverage} label=${JSON.stringify(target.commandLabel)} file=${target.file}`),
|
|
524
533
|
];
|
|
525
534
|
if (result.artifactPath) {
|
|
526
535
|
lines.push(`Artifact: ${result.artifactPath}`);
|
|
@@ -22,9 +22,8 @@ export const frontendWriterAdmissionClassificationSchema = z.enum([
|
|
|
22
22
|
"requires-human-approval",
|
|
23
23
|
"blocked",
|
|
24
24
|
"stale",
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
// emit one of the four values above.
|
|
25
|
+
// `admitted` is retained only as an invalidated input value; the runtime
|
|
26
|
+
// authorization gate accepts only the four canonical classifications above.
|
|
28
27
|
"admitted",
|
|
29
28
|
]);
|
|
30
29
|
/** A+B (AC-006): Micro topology uses `requirement-to-target-and-evidence`
|
|
@@ -59,8 +58,7 @@ export const frontendAdmissionFailureSourceSchema = z.enum([
|
|
|
59
58
|
/**
|
|
60
59
|
* Deterministic artifact schema for `contracts/frontend-writer-admission-result.json`.
|
|
61
60
|
* Scheduler admission gate and frontend recovery planning read this artifact
|
|
62
|
-
* (fail-closed); it
|
|
63
|
-
* write authorization source.
|
|
61
|
+
* (fail-closed); it is the write authorization source for new runs.
|
|
64
62
|
*/
|
|
65
63
|
export const frontendWriterAdmissionResultV1Schema = z
|
|
66
64
|
.object({
|
|
@@ -275,45 +273,3 @@ export function deriveFrontendWriterAdmissionShell(input) {
|
|
|
275
273
|
};
|
|
276
274
|
return result;
|
|
277
275
|
}
|
|
278
|
-
function normalizeNewVerdict(classification) {
|
|
279
|
-
return classification === "accepted" ||
|
|
280
|
-
classification === "requires-human-approval" ||
|
|
281
|
-
classification === "admitted"
|
|
282
|
-
? "accepted"
|
|
283
|
-
: "blocked";
|
|
284
|
-
}
|
|
285
|
-
function normalizeLegacyVerdict(classification) {
|
|
286
|
-
return classification === "accepted" || classification === "accepted-normalized"
|
|
287
|
-
? "accepted"
|
|
288
|
-
: "blocked";
|
|
289
|
-
}
|
|
290
|
-
export function compareAdmissionToLegacyGate(input) {
|
|
291
|
-
const newVerdict = normalizeNewVerdict(input.newClassification);
|
|
292
|
-
const legacyVerdict = normalizeLegacyVerdict(input.legacyClassification);
|
|
293
|
-
return {
|
|
294
|
-
newClassification: input.newClassification,
|
|
295
|
-
legacyClassification: input.legacyClassification,
|
|
296
|
-
newVerdict,
|
|
297
|
-
legacyVerdict,
|
|
298
|
-
match: newVerdict === legacyVerdict,
|
|
299
|
-
};
|
|
300
|
-
}
|
|
301
|
-
/** Render a stable, deterministically ordered mismatch list for the evaluation
|
|
302
|
-
* set. Input order is irrelevant: rows are sorted by a canonical key. */
|
|
303
|
-
export function renderAdmissionShadowDiff(results) {
|
|
304
|
-
const indexed = results.map((result, index) => ({ ...result, index }));
|
|
305
|
-
const sorted = indexed.sort((a, b) => {
|
|
306
|
-
const keyA = `${a.legacyClassification}|${a.newClassification}`;
|
|
307
|
-
const keyB = `${b.legacyClassification}|${b.newClassification}`;
|
|
308
|
-
if (keyA !== keyB)
|
|
309
|
-
return keyA < keyB ? -1 : 1;
|
|
310
|
-
return a.index - b.index;
|
|
311
|
-
});
|
|
312
|
-
const mismatches = sorted.filter((row) => !row.match);
|
|
313
|
-
const lines = [
|
|
314
|
-
"## Admission Shadow Diff",
|
|
315
|
-
`${sorted.length} comparisons, ${mismatches.length} mismatches`,
|
|
316
|
-
...sorted.map((row) => `- ${row.legacyClassification} -> ${row.newClassification}: legacy=${row.legacyVerdict} new=${row.newVerdict}${row.match ? " match" : " MISMATCH"}`),
|
|
317
|
-
];
|
|
318
|
-
return lines.join("\n");
|
|
319
|
-
}
|
|
@@ -161,26 +161,6 @@ export function buildFrontendMicroLoopCheckpoint(input) {
|
|
|
161
161
|
writeAttempts: input.facts.writeAttempts,
|
|
162
162
|
};
|
|
163
163
|
}
|
|
164
|
-
/**
|
|
165
|
-
* Shadow comparison between the fact-derived status and the legacy
|
|
166
|
-
* `IMPLEMENTATION_OUTCOME:` first-line parse. Never used to decide ok/fail —
|
|
167
|
-
* only persisted for the M4 shadow evaluation.
|
|
168
|
-
*/
|
|
169
|
-
export function compareFactStatusToLegacyOutcome(factStatus, legacyOutcome) {
|
|
170
|
-
const factVerdict = factStatus === "changed" || factStatus === "already-satisfied"
|
|
171
|
-
? "ok"
|
|
172
|
-
: "fail";
|
|
173
|
-
const legacyVerdict = legacyOutcome === "changed" || legacyOutcome === "already-satisfied"
|
|
174
|
-
? "ok"
|
|
175
|
-
: "fail";
|
|
176
|
-
return {
|
|
177
|
-
factStatus,
|
|
178
|
-
legacyOutcome,
|
|
179
|
-
factVerdict,
|
|
180
|
-
legacyVerdict,
|
|
181
|
-
match: factVerdict === legacyVerdict,
|
|
182
|
-
};
|
|
183
|
-
}
|
|
184
164
|
/**
|
|
185
165
|
* Pure session-event scanner: count write-tool events from the persisted
|
|
186
166
|
* session-events.jsonl content. Fail-closed: an unparseable line never counts
|
|
@@ -249,8 +229,5 @@ export async function collectFrontendWriterFacts(input) {
|
|
|
249
229
|
wallTimeMs: input.wallTimeMs ?? 0,
|
|
250
230
|
rounds: input.rounds ?? 1,
|
|
251
231
|
writeAttempts: input.writeAttempts ?? 1,
|
|
252
|
-
...(input.firstLineText !== undefined
|
|
253
|
-
? { firstLineText: input.firstLineText }
|
|
254
|
-
: {}),
|
|
255
232
|
};
|
|
256
233
|
}
|