@tea-agent/loop-agent 0.36.4-beta.0 → 0.37.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 +50 -68
- package/dist/build-stamp.json +3 -3
- package/dist/infrastructure/harness/atomic-write.js +1 -2
- package/dist/task/contract/project.js +4 -0
- package/dist/task/frontend-project-capability.js +12 -162
- package/dist/task/source-prepare/completeness.js +36 -0
- package/dist/task/source-prepare/parse-intent.js +67 -11
- package/dist/task/source-prepare/prepare.js +102 -5
- package/dist/worker/console/chat/chat-event-store.js +91 -0
- package/dist/worker/console/chat/pi-runtime.js +25 -0
- package/dist/worker/console/chat/routes.js +7 -2
- package/dist/worker/console/chat/semantic-activity.js +7 -1
- package/dist/worker/console/chat/session-store.js +6 -1
- package/dist/worker/console/chat/turn-process.js +119 -12
- package/dist/worker/console/operator-actions.js +18 -1
- package/dist/worker/console/static/assets/abnfDiagram-N423BO3Z-DSvF6RXQ.js +1 -0
- package/dist/worker/console/static/assets/arc-DuI4ogtJ.js +1 -0
- package/dist/worker/console/static/assets/architectureDiagram-T3A2C74G-DdBbgD4y.js +36 -0
- package/dist/worker/console/static/assets/blockDiagram-VBNYF7ZC-xQC2HELm.js +132 -0
- package/dist/worker/console/static/assets/c4Diagram-5PPSVZJV-DPMSgwIZ.js +10 -0
- package/dist/worker/console/static/assets/channel-DLVz9TYW.js +1 -0
- package/dist/worker/console/static/assets/chunk-2GRJ4B5K-WTEKTxin.js +1 -0
- package/dist/worker/console/static/assets/chunk-2Q5K7J3B-B6vZ0Pv_.js +1 -0
- package/dist/worker/console/static/assets/chunk-5RXB4S5H-BrerNAix.js +231 -0
- package/dist/worker/console/static/assets/chunk-5VM5RSS4-D_ch2aaI.js +15 -0
- package/dist/worker/console/static/assets/chunk-6Q2QTUOP-VvfacNrq.js +88 -0
- package/dist/worker/console/static/assets/chunk-GF5L2VYU-B-uGFIg2.js +206 -0
- package/dist/worker/console/static/assets/chunk-JWPE2WC7-BlysCtod.js +1 -0
- package/dist/worker/console/static/assets/chunk-KBJHAD2P-BjAUAfcT.js +1 -0
- package/dist/worker/console/static/assets/chunk-RYQCIY6F-BeZnBmb-.js +1 -0
- package/dist/worker/console/static/assets/chunk-XXDRQBXY-Bbfe3UJG.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BeAuGcO9.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BeAuGcO9.js +1 -0
- package/dist/worker/console/static/assets/cose-bilkent-JH36ORCC-BKJQnYVM.js +1 -0
- package/dist/worker/console/static/assets/cynefin-VYW2F7L2-B1wtI3hU.js +166 -0
- package/dist/worker/console/static/assets/cynefinDiagram-MW4NZA55-DCF6DBSu.js +62 -0
- package/dist/worker/console/static/assets/cytoscape.esm-yzknjiTM.js +321 -0
- package/dist/worker/console/static/assets/dagre-VZM6K2ZE-BJyNGRlP.js +4 -0
- package/dist/worker/console/static/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/dist/worker/console/static/assets/diagram-7IWD3JNH-B7j1-VsZ.js +30 -0
- package/dist/worker/console/static/assets/diagram-B4RE2ZJO-BREJ140a.js +3 -0
- package/dist/worker/console/static/assets/diagram-LBJQPF4R-CDPHmHGH.js +24 -0
- package/dist/worker/console/static/assets/diagram-Q27KOJAE-BtZLrez1.js +24 -0
- package/dist/worker/console/static/assets/diagram-UB23O5K3-mzybu8oU.js +41 -0
- package/dist/worker/console/static/assets/ebnfDiagram-BXEA7PRR-Dk3I2uO6.js +1 -0
- package/dist/worker/console/static/assets/erDiagram-JOGREHBK-BT97u_tQ.js +85 -0
- package/dist/worker/console/static/assets/flowDiagram-UKHOOZJN-CMOtYzui.js +156 -0
- package/dist/worker/console/static/assets/ganttDiagram-PKOTCBZU-DiNydkuY.js +292 -0
- package/dist/worker/console/static/assets/gitGraphDiagram-DS77QQ5N-Cwt6fLkl.js +106 -0
- package/dist/worker/console/static/assets/graph-DOmOIIwC.js +1 -0
- package/dist/worker/console/static/assets/index-DQxiso1w.js +325 -0
- package/dist/worker/console/static/assets/index-_j0Hzo69.css +1 -0
- package/dist/worker/console/static/assets/infoDiagram-6WML65LV-AsGIGKKB.js +2 -0
- package/dist/worker/console/static/assets/init-Gi6I4Gst.js +1 -0
- package/dist/worker/console/static/assets/ishikawaDiagram-WSZJBQD7-BL8ITZh3.js +70 -0
- package/dist/worker/console/static/assets/journeyDiagram-NVQOT4AX-VPKAUX8M.js +139 -0
- package/dist/worker/console/static/assets/kanban-definition-27J2QSJJ-BL56hfGp.js +89 -0
- package/dist/worker/console/static/assets/layout-D-LzfAck.js +1 -0
- package/dist/worker/console/static/assets/linear-BmSG3nIZ.js +1 -0
- package/dist/worker/console/static/assets/map-DxJ2ADlA.js +1 -0
- package/dist/worker/console/static/assets/mermaid.core-DWGK9kwP.js +308 -0
- package/dist/worker/console/static/assets/mindmap-definition-FAOFIHXS--vvKLjo0.js +96 -0
- package/dist/worker/console/static/assets/ordinal-Cboi1Yqb.js +1 -0
- package/dist/worker/console/static/assets/pegDiagram-VL7TDLO6-B41mxb2-.js +1 -0
- package/dist/worker/console/static/assets/pieDiagram-7S7Q4E2Y-DTlu77sw.js +39 -0
- package/dist/worker/console/static/assets/quadrantDiagram-CIZ2JOQS-Bk5A7alv.js +7 -0
- package/dist/worker/console/static/assets/railroadDiagram-AXF67PYL-Csf9jzQy.js +1 -0
- package/dist/worker/console/static/assets/requirementDiagram-LRYGKXZP-CeptoSNx.js +84 -0
- package/dist/worker/console/static/assets/sankeyDiagram-W5VNT64P-A-DLCLer.js +40 -0
- package/dist/worker/console/static/assets/sequenceDiagram-SI44F4Z6-22VbPdOI.js +162 -0
- package/dist/worker/console/static/assets/sizeCapture-X5ZJPWSS-DWvAyBC9.js +1 -0
- package/dist/worker/console/static/assets/stateDiagram-OKZ733FA-qICZ75yT.js +1 -0
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DzKvfHTX.js +1 -0
- package/dist/worker/console/static/assets/swimlanes-SLNWSIFB-xi7lcMyp.js +2 -0
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Cxqr_qM1.js +8 -0
- package/dist/worker/console/static/assets/timeline-definition-Z64GVDOM-Bu6Nl6O2.js +120 -0
- package/dist/worker/console/static/assets/vennDiagram-T6HMQDX7-BzMz4Bn9.js +34 -0
- package/dist/worker/console/static/assets/wardleyDiagram-T6FBY63Y-CxaJnzc5.js +78 -0
- package/dist/worker/console/static/assets/xychartDiagram-ELKLHX3M-5JK-omQK.js +7 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_AMS-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Caligraphic-Bold.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Caligraphic-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Fraktur-Bold.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Fraktur-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Main-Bold.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Main-BoldItalic.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Main-Italic.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Main-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Math-BoldItalic.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Math-Italic.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_SansSerif-Bold.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_SansSerif-Italic.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_SansSerif-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Script-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Size1-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Size2-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Size3-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Size4-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Typewriter-Regular.woff2 +0 -0
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +25 -0
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +37 -2
- package/dist/worker/observe/static/operator-chrome.js +4 -2
- package/dist/workflows/dag/failure-routing.js +4 -9
- package/dist/workflows/dag/frontend-prewrite-gate.js +8 -37
- package/dist/workflows/dag/init-hybrid.js +4 -73
- package/dist/workflows/dag/lifecycle.js +0 -4
- package/dist/workflows/dag/node-execution.js +6 -16
- package/dist/workflows/dag/report.js +0 -6
- package/dist/workflows/dag/retry-policy.js +0 -11
- package/dist/workflows/dag/runner.js +19 -3
- package/dist/workflows/dag/types.js +0 -2
- package/dist/workflows/dag/validate.js +2 -7
- package/docs/templates/frontend-design-contract.md +14 -34
- package/docs/templates/frontend-task-constraints.md +13 -33
- package/docs/templates/frontend-task-requirement.md +20 -54
- package/package.json +6 -1
- package/skills/loop-agent/references/command-reference.md +1 -0
- package/dist/worker/console/static/assets/index-D83DYAFG.css +0 -1
- package/dist/worker/console/static/assets/index-IXm7oYjL.js +0 -59
|
@@ -44,9 +44,7 @@ function routeToProductLine(input) {
|
|
|
44
44
|
case "decision-envelope":
|
|
45
45
|
return "NeedsHuman";
|
|
46
46
|
case "skipped":
|
|
47
|
-
return
|
|
48
|
-
? "ContractMismatch"
|
|
49
|
-
: "DependencyFailure";
|
|
47
|
+
return "DependencyFailure";
|
|
50
48
|
case "shell-command":
|
|
51
49
|
if (/\b(ENOENT|PATH|command not found|No such file|not found in PATH|bash: .*: No such file)\b/i.test(raw)) {
|
|
52
50
|
return "EnvFailure";
|
|
@@ -92,12 +90,9 @@ export function routeDagFailure(input) {
|
|
|
92
90
|
if (!productLineFailureCategory)
|
|
93
91
|
return {};
|
|
94
92
|
const recommendedFollowUp = productLineFailureCategory === "ContractMismatch" &&
|
|
95
|
-
input.
|
|
96
|
-
? "
|
|
97
|
-
: productLineFailureCategory
|
|
98
|
-
isFrontendDesignGateNode(input.nodeId ?? "")
|
|
99
|
-
? "frontend-plan-revision-and-rerun"
|
|
100
|
-
: FOLLOW_UP_BY_PRODUCT_LINE[productLineFailureCategory];
|
|
93
|
+
isFrontendDesignGateNode(input.nodeId ?? "")
|
|
94
|
+
? "frontend-plan-revision-and-rerun"
|
|
95
|
+
: FOLLOW_UP_BY_PRODUCT_LINE[productLineFailureCategory];
|
|
101
96
|
return {
|
|
102
97
|
productLineFailureCategory,
|
|
103
98
|
recommendedFollowUp,
|
|
@@ -25,7 +25,6 @@ export const frontendPrewriteFailureCodeSchema = z.enum([
|
|
|
25
25
|
"mock-strategy-no-verification-commands",
|
|
26
26
|
"target-outside-write-set",
|
|
27
27
|
"verification-target-outside-write-set",
|
|
28
|
-
"openspec-not-read",
|
|
29
28
|
]);
|
|
30
29
|
export const frontendPrewriteResultV1Schema = z
|
|
31
30
|
.object({
|
|
@@ -499,9 +498,13 @@ export async function runFrontendPrewriteGate(input) {
|
|
|
499
498
|
});
|
|
500
499
|
}
|
|
501
500
|
}
|
|
502
|
-
//
|
|
503
|
-
|
|
504
|
-
|
|
501
|
+
// Materialize the canonical contract only after every governance check passed.
|
|
502
|
+
const artifact = await writeFrontendImplementationContractArtifact({
|
|
503
|
+
runDir: input.runDir,
|
|
504
|
+
outputDir: input.config.outputDir,
|
|
505
|
+
artifactName: input.config.artifactName,
|
|
506
|
+
canonical: analysis.canonical,
|
|
507
|
+
});
|
|
505
508
|
const candidatePaths = input.config.openspecCandidatePaths ?? [];
|
|
506
509
|
const openspecReadPaths = await checkOpenspecReadEvidence({
|
|
507
510
|
runDir: input.runDir,
|
|
@@ -510,31 +513,6 @@ export async function runFrontendPrewriteGate(input) {
|
|
|
510
513
|
reviewNodeId,
|
|
511
514
|
repoRoot: workspaceRoot ?? process.cwd(),
|
|
512
515
|
});
|
|
513
|
-
if (candidatePaths.length > 0) {
|
|
514
|
-
const readSet = new Set(openspecReadPaths);
|
|
515
|
-
const unread = candidatePaths.filter((candidate) => !readSet.has(candidate));
|
|
516
|
-
if (unread.length > 0) {
|
|
517
|
-
return finalizePrewrite(input, {
|
|
518
|
-
...basePending,
|
|
519
|
-
verdict,
|
|
520
|
-
candidateJsonSha256: analysis.candidateJsonSha256,
|
|
521
|
-
normalizationActions: [],
|
|
522
|
-
mockStrategy,
|
|
523
|
-
classification: "retryable-invalid",
|
|
524
|
-
failureReason: `frontend prewrite gate: effective plan/review did not read these openspec candidate paths: ${unread.join(", ")}. Read each candidate and report applicable rules plus hit path/section/line number, conflicts, and missing specifications, then rerun.`,
|
|
525
|
-
failureCode: "openspec-not-read",
|
|
526
|
-
openspecReadPaths,
|
|
527
|
-
openspecCandidatePaths: candidatePaths,
|
|
528
|
-
});
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
// Materialize the canonical contract only after every governance check passed.
|
|
532
|
-
const artifact = await writeFrontendImplementationContractArtifact({
|
|
533
|
-
runDir: input.runDir,
|
|
534
|
-
outputDir: input.config.outputDir,
|
|
535
|
-
artifactName: input.config.artifactName,
|
|
536
|
-
canonical: analysis.canonical,
|
|
537
|
-
});
|
|
538
516
|
const classification = analysis.normalizationActions.length > 0 ? "accepted-normalized" : "accepted";
|
|
539
517
|
return finalizePrewrite(input, {
|
|
540
518
|
...basePending,
|
|
@@ -578,14 +556,7 @@ export function formatFrontendPrewriteGateStdout(result) {
|
|
|
578
556
|
if (result.openspecReadPaths.length > 0) {
|
|
579
557
|
lines.push(`openspec read: ${result.openspecReadPaths.join(", ")}`);
|
|
580
558
|
}
|
|
581
|
-
if (result.openspecCandidatePaths.length
|
|
582
|
-
const readSet = new Set(result.openspecReadPaths);
|
|
583
|
-
const unread = result.openspecCandidatePaths.filter((candidate) => !readSet.has(candidate));
|
|
584
|
-
if (unread.length > 0) {
|
|
585
|
-
lines.push(`openspec unread: ${unread.join(", ")}`);
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
else {
|
|
559
|
+
else if (result.openspecCandidatePaths.length === 0) {
|
|
589
560
|
lines.push("openspec: unavailable");
|
|
590
561
|
}
|
|
591
562
|
return lines.join("\n");
|
|
@@ -9,7 +9,7 @@ import { planMavenVerification, } from "../../verification/maven/index.js";
|
|
|
9
9
|
import { pathMatchesPattern } from "../../shared/git-progress.js";
|
|
10
10
|
import { BASELINE_FORBIDDEN_PATHS } from "./governance-constants.js";
|
|
11
11
|
import { buildDecisionEnvelopePromptContract } from "./decision-envelope.js";
|
|
12
|
-
import { DEFAULT_READ_ONLY_PI_RETRY_POLICY,
|
|
12
|
+
import { DEFAULT_READ_ONLY_PI_RETRY_POLICY, PROTOCOL_AWARE_PI_RETRY_POLICY, STRUCTURED_REQUIRED_PI_RETRY_POLICY, BACKEND_TEST_WRITER_COMPLETENESS_RETRY_POLICY, WRITER_TRANSPORT_RETRY_POLICY, isSafeReadOnlyPiRetryCandidate, isWriterTransportRetryCandidate, } from "./retry-policy.js";
|
|
13
13
|
import { REVIEW_JSON_VERDICT_OUTPUT_PROTOCOL, REVIEW_VERDICT_OUTPUT_PROTOCOL, } from "./output-protocol.js";
|
|
14
14
|
import { resolveAdapter } from "../../adapters/index.js";
|
|
15
15
|
import { loadHarnessManifest } from "../../governance/harness.js";
|
|
@@ -558,39 +558,6 @@ export function hasApiDependency(sources) {
|
|
|
558
558
|
.some((clause) => !negationPatterns.some((pattern) => pattern.test(clause)) &&
|
|
559
559
|
dependencyPatterns.some((pattern) => pattern.test(clause)));
|
|
560
560
|
}
|
|
561
|
-
/**
|
|
562
|
-
* Generation-time heuristic: does the task source mention interface/API/Mock
|
|
563
|
-
* needs anywhere (requirement or constraints)?
|
|
564
|
-
*
|
|
565
|
-
* Mirrors hasApiDependency's clause-level negation filtering so explicit
|
|
566
|
-
* "not involved / not implementing" clauses and out-of-scope sections do not
|
|
567
|
-
* become positive evidence. Used to emit a machine-visible advisory when auto
|
|
568
|
-
* mode narrows Mock to not-needed despite the source mentioning such needs.
|
|
569
|
-
*/
|
|
570
|
-
export function frontendSourceMentionsMock(sources) {
|
|
571
|
-
const text = [sources.requirementMarkdown, sources.constraintMarkdown]
|
|
572
|
-
.filter((entry) => typeof entry === "string" && entry.trim().length > 0)
|
|
573
|
-
.map((entry) => normalizeTaskRequirementText(entry).replace(/`[^`\n]*`/g, " "))
|
|
574
|
-
.join("\n");
|
|
575
|
-
const signalPatterns = [
|
|
576
|
-
/mock/i,
|
|
577
|
-
/模拟服务/,
|
|
578
|
-
/接口桩/,
|
|
579
|
-
/\bmsw\b/i,
|
|
580
|
-
/(?<![A-Za-z0-9_])API(?![A-Za-z0-9_])/i,
|
|
581
|
-
/接口/,
|
|
582
|
-
];
|
|
583
|
-
const negationPatterns = [
|
|
584
|
-
/(?:不涉及|无需|不需要|不依赖|不调用|不请求|不实现|没有|禁止|不得).{0,16}(?:接口|后端|服务端|远程数据|异步数据|API|mock|模拟|桩)/i,
|
|
585
|
-
/\b(?:no|without|does\s+not|do\s+not|must\s+not)\b.{0,24}\b(?:api|endpoint|request|backend|server|mock)\b/i,
|
|
586
|
-
];
|
|
587
|
-
return text
|
|
588
|
-
.split(/[。!?!?;;,,\r\n]+/)
|
|
589
|
-
.map((clause) => clause.trim())
|
|
590
|
-
.filter(Boolean)
|
|
591
|
-
.some((clause) => !negationPatterns.some((pattern) => pattern.test(clause)) &&
|
|
592
|
-
signalPatterns.some((pattern) => pattern.test(clause)));
|
|
593
|
-
}
|
|
594
561
|
/**
|
|
595
562
|
* Resolve frontend Mock mode from capability seed, task config, and interface dependency analysis.
|
|
596
563
|
*
|
|
@@ -2144,7 +2111,6 @@ function resolveFrontendMockContextBlock(sources) {
|
|
|
2144
2111
|
parts.push("Generation-time evidence does not require Mock. The assessment must still use contract/scout evidence: select not-needed when Mock is intentionally skipped, or select a safe Mock strategy if project evidence supports one.");
|
|
2145
2112
|
if (frontendMockStrategyMustBeNotNeeded(sources)) {
|
|
2146
2113
|
parts.push('Auto mode has no confirmed project Mock capability or no deterministic Mock verification command. The structured contract must set mockApi.strategy to "not-needed". Do not add Mock files or dependencies; keep the real request path as default and record any unproved backend behavior as Real Integration Gap.');
|
|
2147
|
-
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: declare frontendMock.verifyCommands (or policy: "required") in task.json and regenerate the DAG. Do not emit any mockApi.strategy outside the allowlist and do not add Mock files or dependencies within this run.');
|
|
2148
2114
|
}
|
|
2149
2115
|
}
|
|
2150
2116
|
if (mode === "blocked") {
|
|
@@ -2175,33 +2141,9 @@ function resolveFrontendCapabilityContextBlock(sources) {
|
|
|
2175
2141
|
}
|
|
2176
2142
|
if (capability) {
|
|
2177
2143
|
parts.push("", capability.adapterGuidance);
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
const pushBucket = (label, paths) => {
|
|
2181
|
-
if (paths.length > 0)
|
|
2182
|
-
bucketLines.push(`${label}: ${paths.join(", ")}`);
|
|
2183
|
-
};
|
|
2184
|
-
pushBucket("schemas", classified.schemas);
|
|
2185
|
-
pushBucket("code-template", classified.codeTemplate);
|
|
2186
|
-
pushBucket("rule.api", classified.rule.api);
|
|
2187
|
-
pushBucket("rule.mock", classified.rule.mock);
|
|
2188
|
-
pushBucket("rule.router", classified.rule.router);
|
|
2189
|
-
pushBucket("rule.hooks", classified.rule.hooks);
|
|
2190
|
-
pushBucket("rule.utils", classified.rule.utils);
|
|
2191
|
-
pushBucket("rule.components", classified.rule.components);
|
|
2192
|
-
pushBucket("rule.other", classified.rule.other);
|
|
2193
|
-
pushBucket("theme", classified.theme);
|
|
2194
|
-
pushBucket("component", classified.component);
|
|
2195
|
-
pushBucket("ui-other", classified.uiOther);
|
|
2196
|
-
pushBucket("advisory-other", classified.advisoryOther);
|
|
2197
|
-
parts.push("## Classified openspec specification paths (role semantics)");
|
|
2198
|
-
if (bucketLines.length > 0) {
|
|
2199
|
-
parts.push(...bucketLines);
|
|
2200
|
-
}
|
|
2201
|
-
else {
|
|
2202
|
-
parts.push("(no openspec specification paths discovered — greenfield)");
|
|
2144
|
+
if (capability.designEvidence.normativePaths.length > 0) {
|
|
2145
|
+
parts.push(`openspec normative candidates: ${capability.designEvidence.normativePaths.slice(0, 12).join(", ")}`);
|
|
2203
2146
|
}
|
|
2204
|
-
parts.push("Each consuming node MUST report in its output: applicable rules, the hit path/section/line number for every applied specification, and any conflicts or missing specifications. Missing or conflicting required specifications must fail closed rather than silently substituting nearby repository conventions.");
|
|
2205
2147
|
parts.push(`A11y capability: ${capability.a11y.status}` +
|
|
2206
2148
|
(capability.a11y.tools.length
|
|
2207
2149
|
? ` (${capability.a11y.tools.join(", ")})`
|
|
@@ -2567,11 +2509,6 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2567
2509
|
`- Behavior command source: ${behaviorVerifyEvidence.commandSource}`,
|
|
2568
2510
|
...behaviorVerifyEvidence.commandLabels.map((command) => ` - ${JSON.stringify(command)}`),
|
|
2569
2511
|
].join("\n");
|
|
2570
|
-
const advisories = [];
|
|
2571
|
-
if (frontendSourceMentionsMock(frontendSources) &&
|
|
2572
|
-
frontendMockStrategyMustBeNotNeeded(frontendSources)) {
|
|
2573
|
-
advisories.push("auto 模式已将 Mock 策略收窄为 not-needed:任务源提到接口/API/Mock 需求,但仓库无确认 Mock 能力或无确定性 Mock 验证命令。若项目规范要求 Mock,请声明 frontendMock.verifyCommands 或 policy:required 后重新生成 DAG。");
|
|
2574
|
-
}
|
|
2575
2512
|
const spec = {
|
|
2576
2513
|
version: 3,
|
|
2577
2514
|
title: `Frontend implementation DAG: ${taskConfig.title}`,
|
|
@@ -2586,7 +2523,6 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2586
2523
|
skillsByRole: FRONTEND_SKILLS_BY_ROLE,
|
|
2587
2524
|
executorModels: sources.executorModelMatrix ?? DEFAULT_DAG_EXECUTOR_MODELS,
|
|
2588
2525
|
verifyStrategy: resolveDagVerifyStrategy(taskConfig),
|
|
2589
|
-
advisories: advisories.length > 0 ? advisories : undefined,
|
|
2590
2526
|
tasks: [
|
|
2591
2527
|
{
|
|
2592
2528
|
id: "frontend-contract-pi",
|
|
@@ -2681,7 +2617,6 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2681
2617
|
"Read-only: do not modify repository files.",
|
|
2682
2618
|
fixedVerificationContext,
|
|
2683
2619
|
sourceContext,
|
|
2684
|
-
mockContextBlock,
|
|
2685
2620
|
].join("\n\n"),
|
|
2686
2621
|
},
|
|
2687
2622
|
{
|
|
@@ -2716,7 +2651,6 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2716
2651
|
fixedVerificationContext,
|
|
2717
2652
|
sourceContext,
|
|
2718
2653
|
frontendContractSchemaBlock,
|
|
2719
|
-
mockContextBlock,
|
|
2720
2654
|
].join("\n\n"),
|
|
2721
2655
|
},
|
|
2722
2656
|
{
|
|
@@ -2747,7 +2681,6 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2747
2681
|
"Read-only: do not modify repository files.",
|
|
2748
2682
|
fixedVerificationContext,
|
|
2749
2683
|
sourceContext,
|
|
2750
|
-
mockContextBlock,
|
|
2751
2684
|
].join("\n\n"),
|
|
2752
2685
|
},
|
|
2753
2686
|
{
|
|
@@ -5850,9 +5783,7 @@ function applyDefaultReadOnlyRetryPolicy(spec) {
|
|
|
5850
5783
|
if (isSafeReadOnlyPiRetryCandidate(task)) {
|
|
5851
5784
|
task.retryPolicy = task.outputProtocol
|
|
5852
5785
|
? PROTOCOL_AWARE_PI_RETRY_POLICY
|
|
5853
|
-
:
|
|
5854
|
-
? PLANNER_OUTPUT_LIMIT_RETRY_POLICY
|
|
5855
|
-
: DEFAULT_READ_ONLY_PI_RETRY_POLICY;
|
|
5786
|
+
: DEFAULT_READ_ONLY_PI_RETRY_POLICY;
|
|
5856
5787
|
continue;
|
|
5857
5788
|
}
|
|
5858
5789
|
if (isWriterTransportRetryCandidate(task)) {
|
|
@@ -743,7 +743,6 @@ function findDoctorFailureNode(state) {
|
|
|
743
743
|
nodeId: state.pausedByNodeId,
|
|
744
744
|
status: node?.status,
|
|
745
745
|
rawFailureCategory: node?.failureCategory,
|
|
746
|
-
skippedReason: node?.skippedReason,
|
|
747
746
|
};
|
|
748
747
|
}
|
|
749
748
|
const errorEntry = Object.entries(state.nodes).find(([, node]) => node.status === "ERROR");
|
|
@@ -759,7 +758,6 @@ function findDoctorFailureNode(state) {
|
|
|
759
758
|
nodeId: selected[0],
|
|
760
759
|
status: selected[1].status,
|
|
761
760
|
rawFailureCategory: selected[1].failureCategory,
|
|
762
|
-
skippedReason: selected[1].skippedReason,
|
|
763
761
|
};
|
|
764
762
|
}
|
|
765
763
|
async function readRunOwnedBackendTestClassification(runDir) {
|
|
@@ -788,7 +786,6 @@ async function resolveDoctorFailureRouting(input) {
|
|
|
788
786
|
normalizedFailureCategory: "unknown",
|
|
789
787
|
nodeId: input.nodeId,
|
|
790
788
|
productLineFailureCategory: classifiedCategory,
|
|
791
|
-
skippedReason: input.skippedReason,
|
|
792
789
|
});
|
|
793
790
|
}
|
|
794
791
|
return routeDagFailure(input);
|
|
@@ -810,7 +807,6 @@ async function formatDagDoctorMarkdown(repoRoot, runId) {
|
|
|
810
807
|
rawFailureCategory,
|
|
811
808
|
normalizedFailureCategory: normalizedCategory,
|
|
812
809
|
nodeId: failure.nodeId,
|
|
813
|
-
skippedReason: failure.skippedReason,
|
|
814
810
|
});
|
|
815
811
|
const evidence = failure.nodeId
|
|
816
812
|
? path.join(located.runDir, failure.nodeId, "result.summary.md")
|
|
@@ -216,28 +216,18 @@ function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFailureCate
|
|
|
216
216
|
"</retry_instruction>",
|
|
217
217
|
].join("\n");
|
|
218
218
|
}
|
|
219
|
-
if (
|
|
219
|
+
if (task.outputMode !== "structured-required" ||
|
|
220
|
+
previousFailureCategory !== "output-too-large") {
|
|
220
221
|
return basePrompt;
|
|
221
222
|
}
|
|
222
|
-
if (task.outputMode === "structured-required") {
|
|
223
|
-
return [
|
|
224
|
-
basePrompt,
|
|
225
|
-
"",
|
|
226
|
-
"<retry_instruction>",
|
|
227
|
-
"Previous attempt exceeded the structured output size limit.",
|
|
228
|
-
"Return only the compact structured artifact required by this node's output contract.",
|
|
229
|
-
"Do not include explanatory prose, duplicated upstream context, long evidence excerpts, or additional markdown sections.",
|
|
230
|
-
"If a fenced JSON object is required, output exactly one fenced json block and nothing else.",
|
|
231
|
-
"</retry_instruction>",
|
|
232
|
-
].join("\n");
|
|
233
|
-
}
|
|
234
223
|
return [
|
|
235
224
|
basePrompt,
|
|
236
225
|
"",
|
|
237
226
|
"<retry_instruction>",
|
|
238
|
-
"Previous attempt exceeded the output size limit.",
|
|
239
|
-
"Return
|
|
240
|
-
"
|
|
227
|
+
"Previous attempt exceeded the structured output size limit.",
|
|
228
|
+
"Return only the compact structured artifact required by this node's output contract.",
|
|
229
|
+
"Do not include explanatory prose, duplicated upstream context, long evidence excerpts, or additional markdown sections.",
|
|
230
|
+
"If a fenced JSON object is required, output exactly one fenced json block and nothing else.",
|
|
241
231
|
"</retry_instruction>",
|
|
242
232
|
].join("\n");
|
|
243
233
|
}
|
|
@@ -441,7 +441,6 @@ export async function buildDagRunReportEntry(input) {
|
|
|
441
441
|
normalizedFailureCategory,
|
|
442
442
|
nodeId,
|
|
443
443
|
executor: node.executor,
|
|
444
|
-
skippedReason: node.skippedReason,
|
|
445
444
|
});
|
|
446
445
|
const followUp = node.status === "ERROR" || node.status === "SKIPPED"
|
|
447
446
|
? recommendFollowUpForFailureCategory(node.failureCategory)
|
|
@@ -506,7 +505,6 @@ export async function buildDagRunReportEntry(input) {
|
|
|
506
505
|
normalizedFailureCategory: normalizeDagFailureCategory(pausedNode.failureCategory, pausedNode.status),
|
|
507
506
|
failureCategory: pausedNode.failureCategory,
|
|
508
507
|
nodeId: input.state.pausedByNodeId,
|
|
509
|
-
skippedReason: pausedNode.skippedReason,
|
|
510
508
|
}
|
|
511
509
|
: firstActionableNode
|
|
512
510
|
? {
|
|
@@ -515,7 +513,6 @@ export async function buildDagRunReportEntry(input) {
|
|
|
515
513
|
normalizeDagFailureCategory(firstActionableNode.failureCategory, firstActionableNode.status),
|
|
516
514
|
failureCategory: firstActionableNode.failureCategory,
|
|
517
515
|
nodeId: firstActionableNode.nodeId,
|
|
518
|
-
skippedReason: input.state.nodes[firstActionableNode.nodeId]?.skippedReason,
|
|
519
516
|
}
|
|
520
517
|
: {
|
|
521
518
|
status: input.state.status,
|
|
@@ -532,9 +529,6 @@ export async function buildDagRunReportEntry(input) {
|
|
|
532
529
|
rawFailureCategory: runRecoverySource.failureCategory,
|
|
533
530
|
normalizedFailureCategory: runRecoverySource.normalizedFailureCategory,
|
|
534
531
|
nodeId: "nodeId" in runRecoverySource ? runRecoverySource.nodeId : undefined,
|
|
535
|
-
skippedReason: "skippedReason" in runRecoverySource
|
|
536
|
-
? runRecoverySource.skippedReason
|
|
537
|
-
: undefined,
|
|
538
532
|
});
|
|
539
533
|
const runFollowUp = input.state.status === "failed" ||
|
|
540
534
|
input.state.status === "partial_failed"
|
|
@@ -118,17 +118,6 @@ export const STRUCTURED_REQUIRED_PI_RETRY_POLICY = {
|
|
|
118
118
|
...DEFAULT_READ_ONLY_PI_RETRY_POLICY,
|
|
119
119
|
retryCategories: [...STRUCTURED_REQUIRED_DAG_RETRY_CATEGORIES],
|
|
120
120
|
};
|
|
121
|
-
/**
|
|
122
|
-
* Planner read-only nodes (standard DAG contract-pi / plan-pi) may retry with
|
|
123
|
-
* a compact-output instruction when the model produced an oversized assistant
|
|
124
|
-
* response. Only `output-too-large` is added on top of the default set;
|
|
125
|
-
* report/review and other read-only roles keep the default set so they do not
|
|
126
|
-
* silently learn new output semantics.
|
|
127
|
-
*/
|
|
128
|
-
export const PLANNER_OUTPUT_LIMIT_RETRY_POLICY = {
|
|
129
|
-
...DEFAULT_READ_ONLY_PI_RETRY_POLICY,
|
|
130
|
-
retryCategories: [...DEFAULT_DAG_RETRY_CATEGORIES, STRUCTURED_OUTPUT_RETRY_CATEGORY],
|
|
131
|
-
};
|
|
132
121
|
/**
|
|
133
122
|
* Default retry for reviewer / recovery nodes that declare outputProtocol.
|
|
134
123
|
* Includes protocol-invalid so missing VERDICT lines are corrected in-node.
|
|
@@ -115,6 +115,22 @@ export async function buildDagRunId(spec, options) {
|
|
|
115
115
|
const existing = await listExistingDagRunIds(options.cwd);
|
|
116
116
|
return collisionSafeDagRunId(baseId, existing, now);
|
|
117
117
|
}
|
|
118
|
+
/**
|
|
119
|
+
* Append a human-readable round label to a run title when the runId carries
|
|
120
|
+
* a `-rN` collision suffix (2026-08-16): the sidebar / Inspect list showed
|
|
121
|
+
* identical titles for reruns and only the id hinted at the round.
|
|
122
|
+
*/
|
|
123
|
+
export function titleWithRoundLabel(title, runId) {
|
|
124
|
+
const match = /-r(\d{1,2})$/.exec(runId);
|
|
125
|
+
if (!match)
|
|
126
|
+
return title;
|
|
127
|
+
const round = Number.parseInt(match[1], 10);
|
|
128
|
+
if (!Number.isFinite(round) || round < 2)
|
|
129
|
+
return title;
|
|
130
|
+
// Never stack labels on reruns of reruns.
|
|
131
|
+
const base = title.replace(/\s*(第\s*\d+\s*轮)$/u, "");
|
|
132
|
+
return `${base}(第 ${round} 轮)`;
|
|
133
|
+
}
|
|
118
134
|
export function createInitialRunState(spec, opts, ranks, runId = opts.runId ?? "dag") {
|
|
119
135
|
const nodes = {};
|
|
120
136
|
for (const task of spec.tasks) {
|
|
@@ -136,7 +152,7 @@ export function createInitialRunState(spec, opts, ranks, runId = opts.runId ?? "
|
|
|
136
152
|
: "execute";
|
|
137
153
|
const state = {
|
|
138
154
|
version: 1,
|
|
139
|
-
title: spec.title,
|
|
155
|
+
title: titleWithRoundLabel(spec.title, runId),
|
|
140
156
|
runId,
|
|
141
157
|
cwd: opts.cwd,
|
|
142
158
|
startedAt: new Date().toISOString(),
|
|
@@ -263,7 +279,7 @@ export async function runDag(spec, opts) {
|
|
|
263
279
|
}
|
|
264
280
|
if (opts.dryRun || opts.initOnly) {
|
|
265
281
|
return {
|
|
266
|
-
title:
|
|
282
|
+
title: state.title,
|
|
267
283
|
runId: state.runId,
|
|
268
284
|
status: state.status,
|
|
269
285
|
ranks,
|
|
@@ -717,7 +733,7 @@ async function executeDagCheckpoint(input) {
|
|
|
717
733
|
await writeBudgetLedgerArtifacts(runDir, state.budgetLedger);
|
|
718
734
|
await persistState({ allowCompletedFactsWrite: true });
|
|
719
735
|
return {
|
|
720
|
-
title:
|
|
736
|
+
title: state.title,
|
|
721
737
|
runId: state.runId,
|
|
722
738
|
status: state.status,
|
|
723
739
|
ranks,
|
|
@@ -822,8 +822,6 @@ export const dagSpecSchema = z
|
|
|
822
822
|
defaults: dagDefaultsSchema,
|
|
823
823
|
skillsByRole: z.record(z.string(), z.array(z.string())).optional(),
|
|
824
824
|
executorModels: dagExecutorModelsSchema.optional(),
|
|
825
|
-
/** Generation-time advisories surfaced as machine-visible warnings. */
|
|
826
|
-
advisories: z.array(z.string()).optional(),
|
|
827
825
|
tasks: z.array(dagTaskSchema).min(1),
|
|
828
826
|
})
|
|
829
827
|
.superRefine((spec, ctx) => {
|
|
@@ -14,7 +14,6 @@ const GOVERNANCE_WARNING_TYPES = new Set([
|
|
|
14
14
|
"shell-verdict-gate-multi-command-state",
|
|
15
15
|
"shell-verdict-gate-handwritten-inline",
|
|
16
16
|
"orphan-writer-source-binding",
|
|
17
|
-
"advisory",
|
|
18
17
|
]);
|
|
19
18
|
const ROOT_ARTIFACT_PATH_PROBES = [
|
|
20
19
|
"artifacts/修改记录.md",
|
|
@@ -33,11 +32,10 @@ export function collectGovernanceWarnings(issues) {
|
|
|
33
32
|
return issues.filter(isGovernanceWarning);
|
|
34
33
|
}
|
|
35
34
|
function collectBlockingIssues(issues, options = {}) {
|
|
36
|
-
const nonAdvisory = issues.filter((issue) => issue.type !== "advisory");
|
|
37
35
|
if (options.strictGovernance) {
|
|
38
|
-
return
|
|
36
|
+
return [...issues];
|
|
39
37
|
}
|
|
40
|
-
return
|
|
38
|
+
return issues.filter((issue) => !isGovernanceWarning(issue));
|
|
41
39
|
}
|
|
42
40
|
function normalizePath(value) {
|
|
43
41
|
return value.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
@@ -958,9 +956,6 @@ export function validateDagSpec(spec) {
|
|
|
958
956
|
validateSameRankWriteSetConflicts(spec, ranks, issues);
|
|
959
957
|
validateSameRankAgentAttributionRisks(spec, ranks, issues);
|
|
960
958
|
validateWriterSourceBinding(spec, issues);
|
|
961
|
-
for (const advisory of spec.advisories ?? []) {
|
|
962
|
-
issues.push({ type: "advisory", message: advisory });
|
|
963
|
-
}
|
|
964
959
|
return issues;
|
|
965
960
|
}
|
|
966
961
|
function validateProjectGovernanceTaskConfig(task, spec, issues) {
|
|
@@ -1,62 +1,42 @@
|
|
|
1
1
|
# 前端设计契约模板
|
|
2
2
|
|
|
3
|
-
本模板只承载**通用编写协议 + 三要素 + fail-closed 规则**,不包含任何具体业务组件名、项目专属路径或项目专属命令。设计契约在进入实现前由 `frontend-plan-pi` 落入结构化 implementation contract,`frontend-design-review-pi` 按本模板逐节审查;每一节都要给出「**要写什么** / **DAG 如何消费** / **缺失/冲突时 fail-closed**」三要素。
|
|
4
|
-
|
|
5
3
|
## 页面目标
|
|
6
4
|
|
|
7
|
-
|
|
8
|
-
- **DAG 如何消费**:`frontend-plan-pi` 据它确定 `targets.routes` 与每页的实现步骤,`frontend-design-review-pi` 据它核对范围与需求一致性。
|
|
9
|
-
- **缺失/冲突时 fail-closed**:页面目标与需求不一致、缺页或目标含糊(无成功结果)时阻塞,不得自行补页或改目标。
|
|
5
|
+
TODO
|
|
10
6
|
|
|
11
7
|
## 信息结构
|
|
12
8
|
|
|
13
|
-
|
|
14
|
-
- **DAG 如何消费**:`frontend-plan-pi` 据它安排组件数据流与状态展示,`frontend-design-review-pi` 据它核对接口字段映射。
|
|
15
|
-
- **缺失/冲突时 fail-closed**:信息层级缺失、数据来源不可追溯、与接口字段冲突时阻塞,不得臆造字段或层级。
|
|
9
|
+
TODO
|
|
16
10
|
|
|
17
11
|
## 组件拆分
|
|
18
12
|
|
|
19
|
-
|
|
20
|
-
- **DAG 如何消费**:`frontend-plan-pi` 据它确定 `targets.files` 与实现顺序,`frontend-design-review-pi` 据它审查拆分是否越界/重复。
|
|
21
|
-
- **缺失/冲突时 fail-closed**:组件边界不清、同一职责被拆到多处、或与设计规范冲突时阻塞,不得按邻近代码自行决定复用。
|
|
13
|
+
TODO
|
|
22
14
|
|
|
23
15
|
## 交互规则
|
|
24
16
|
|
|
25
|
-
|
|
26
|
-
- **DAG 如何消费**:`frontend-plan-pi` 据它填充 `interactions[]`(`trigger` + `expectedBehavior`),verify 用行为命令断言。
|
|
27
|
-
- **缺失/冲突时 fail-closed**:交互无预期行为、与 UI 状态冲突、或不可自动化断言时阻塞。
|
|
17
|
+
TODO
|
|
28
18
|
|
|
29
19
|
## UI 状态
|
|
30
20
|
|
|
31
|
-
|
|
32
|
-
- **DAG 如何消费**:进入 implementation contract 的 `uiStates[]`,verify 逐状态断言,`frontend-review-pi` 据它核对实现是否遗漏状态。
|
|
33
|
-
- **缺失/冲突时 fail-closed**:适用状态缺失预期、N/A 无理由、或状态展示与交互/接口状态冲突时阻塞。
|
|
21
|
+
TODO
|
|
34
22
|
|
|
35
23
|
## Mock / API 策略
|
|
36
24
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
-
|
|
40
|
-
-
|
|
41
|
-
-
|
|
42
|
-
-
|
|
43
|
-
- **DAG 已固化的验证入口 — 要写什么**:引用 DAG 生成时已冻结的 static/behavior/Mock 命令 label,不发明新命令。**DAG 如何消费**:`verificationTarget.commandLabel` 必须逐字落在冻结命令集合内,否则 contract 物化 `invalid-output`。**缺失/冲突时 fail-closed**:策略需要 Mock 验证命令却没有冻结命令时生成期 fail-closed(`no authorized Mock verification commands`)。
|
|
44
|
-
- **Real Integration Gap 与后端就绪后的复验 — 要写什么**:写清当前未联通的真实集成缺口,以及后端就绪后的复验路径。**DAG 如何消费**:进入 `evidenceGaps[]`,closeout 报告 `Real integration: pending`,复验任务 `<task-id>-real-api-integration-verify` 由操作者显式触发。**缺失/冲突时 fail-closed**:把 Mock 证据当真实联调证据时 review 拒绝。
|
|
25
|
+
- 接口文档或 schema:TODO
|
|
26
|
+
- 策略:`native | browser-intercept | request-adapter | not-needed | blocked`
|
|
27
|
+
- endpoint / fixture / UI 状态映射:TODO
|
|
28
|
+
- 显式启用方式与 production 默认关闭边界:TODO
|
|
29
|
+
- DAG 已固化的验证入口:TODO
|
|
30
|
+
- Real Integration Gap 与后端就绪后的复验:TODO
|
|
45
31
|
|
|
46
32
|
## 样式与设计系统映射
|
|
47
33
|
|
|
48
|
-
|
|
49
|
-
- **DAG 如何消费**:`frontend-plan-pi` 据它安排样式实现,`frontend-design-review-pi` 据它核对是否与设计系统冲突。
|
|
50
|
-
- **缺失/冲突时 fail-closed**:样式来源缺失、Token/组件冲突、或自定义样式会破坏设计系统时阻塞。
|
|
34
|
+
TODO
|
|
51
35
|
|
|
52
36
|
## 响应式范围
|
|
53
37
|
|
|
54
|
-
|
|
55
|
-
- **DAG 如何消费**:进入 contract 的 target runtime environment,`frontend-plan-pi` 据它决定响应式策略。
|
|
56
|
-
- **缺失/冲突时 fail-closed**:视口范围与需求运行环境不一致、或断点未覆盖声明环境时阻塞。
|
|
38
|
+
TODO
|
|
57
39
|
|
|
58
40
|
## 风险与非目标
|
|
59
41
|
|
|
60
|
-
|
|
61
|
-
- **DAG 如何消费**:进入 `evidenceGaps[]` 与 Non-goals,`frontend-review-pi`/closeout 据它保留风险与后续项。
|
|
62
|
-
- **缺失/冲突时 fail-closed**:范围与需求非目标冲突、或风险被隐藏时阻塞。
|
|
42
|
+
TODO
|
|
@@ -1,55 +1,35 @@
|
|
|
1
1
|
# 前端任务执行约束模板
|
|
2
2
|
|
|
3
|
-
本模板只承载**通用探索协议 + 结果 schema + 缺失/冲突时 fail-closed 规则**,不包含任何具体业务组件名、项目专属路径或项目专属命令。项目事实由生成期能力探测(`openspec/schemas/**`、`openspec/project-specs/**`、`ai_workspace/**`)产出并经语义归类注入任务契约。
|
|
4
|
-
|
|
5
3
|
## 技术约束
|
|
6
4
|
|
|
7
|
-
|
|
8
|
-
- **结果必须包含**:每一条适用技术规则的源路径、命中章节与行号;被排除依赖的明确理由;与既有技术栈一致的运行时要求(编译、构建、入口、环境变量)。
|
|
9
|
-
- **缺失/冲突时如何 fail-closed**:无法找到技术规范来源时,显式标注 `source: unavailable` 并只采用任务源与现有代码的可验证事实;发现冲突规则时不臆造取舍,先记录冲突并返回 `request-revision`/blocked,而不是静默采用邻近代码惯例。
|
|
5
|
+
TODO
|
|
10
6
|
|
|
11
7
|
## 代码风格约束
|
|
12
8
|
|
|
13
|
-
|
|
14
|
-
- **结果必须包含**:命中模板/规则的源路径、章节与行号;命名、目录、导出、类型、样式组织等可执行约定;冲突字段逐条列出。
|
|
15
|
-
- **缺失/冲突时如何 fail-closed**:没有模板/规则命中时明确说明并记录 `repository fallback` 证据;冲突未解决时不得进入实现,必须返回阻塞信息。
|
|
9
|
+
TODO
|
|
16
10
|
|
|
17
11
|
## 设计约束
|
|
18
12
|
|
|
19
|
-
|
|
20
|
-
- **结果必须包含**:命中设计规范/组件目录的源路径、章节与行号;组件拆分与复用边界、样式与设计系统映射、UI 状态与响应式范围。
|
|
21
|
-
- **缺失/冲突时如何 fail-closed**:设计规范缺失时明确记录来源为 `unavailable` 并保留为已知风险;组件/主题冲突时必须阻断,不能按邻近代码自行决定。
|
|
13
|
+
TODO
|
|
22
14
|
|
|
23
15
|
## 验证约束
|
|
24
16
|
|
|
25
|
-
|
|
26
|
-
- **结果必须包含**:命令的 label、原始命令文本与来源(scripts/任务源);每条命令可自终止(启动→断言→退出)的说明。
|
|
27
|
-
- **缺失/冲突时如何 fail-closed**:只在确定命令真实存在时写入冻结命令集;不发明 shell 命令。命令不可执行或漂移时 fail closed,不宣称验证通过。
|
|
17
|
+
TODO
|
|
28
18
|
|
|
29
19
|
## Mock 约束(数据型任务)
|
|
30
20
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
-
|
|
34
|
-
-
|
|
35
|
-
-
|
|
36
|
-
-
|
|
37
|
-
-
|
|
38
|
-
- **既有 Mock service root、handler/fixture/bootstrap**:检索项目现有 Mock 服务根目录、handler、fixture 与 bootstrap 启动方式;结果必须列出发现路径,未发现时明确记录 `unavailable`。
|
|
39
|
-
- **既有 browser/e2e interception 或 request adapter/DI seam**:检索项目现有浏览器/e2e 拦截或请求适配层/依赖注入 seam;结果必须列出发现路径或明确记录缺失。
|
|
40
|
-
- **production 禁用边界**:结果必须说明真实请求为默认路径、Mock 仅通过显式测试/开发开关启用的具体边界;无法证明生产默认关闭时返回 `blocked`。
|
|
41
|
-
- **真实请求默认路径与 Mock 显式启用方式**:结果必须写出真实请求默认路径与 Mock 显式启用方式(如环境变量/构建开关),不得通过注释真实请求或在生产组件内硬编码假数据实现。
|
|
42
|
-
- **`task.json.frontendMock.policy`**:`auto | required | disabled`(规范强制 Mock 用 `required`)
|
|
43
|
-
- **被拦截时怎么修**:`mock-strategy-outside-allowed` / `no authorized Mock verification commands` 是**生成期契约问题,不是 plan 问题**——补 `frontendMock.verifyCommands`(或 `policy: "required"` + 命令)后**重新生成 DAG** 再跑,plan-revision 无法修复它。
|
|
21
|
+
- Mock/API/schema 规范路径:TODO
|
|
22
|
+
- 既有 Mock service root、handler/fixture/bootstrap:TODO
|
|
23
|
+
- 既有 browser/e2e interception 或 request adapter/DI seam:TODO
|
|
24
|
+
- 启动、健康检查和专项验证命令:TODO
|
|
25
|
+
- production 禁用边界:TODO
|
|
26
|
+
- 真实请求默认路径与 Mock 显式启用方式:TODO
|
|
27
|
+
- `task.json.frontendMock.policy`:`auto | required | disabled`
|
|
44
28
|
|
|
45
29
|
## allowedPaths
|
|
46
30
|
|
|
47
|
-
|
|
48
|
-
- **结果必须包含**:与 `task.json.allowedPaths` 一致且窄化的路径列表;任何越界路径都必须在 plan 中记为阻断性 scope conflict。
|
|
49
|
-
- **缺失/冲突时如何 fail-closed**:缺失允许路径或发现计划目标在允许路径之外时,不得擅自扩大边界,返回 blocked。
|
|
31
|
+
TODO
|
|
50
32
|
|
|
51
33
|
## forbiddenPaths
|
|
52
34
|
|
|
53
|
-
|
|
54
|
-
- **结果必须包含**:禁止写入的路径清单,以及实现/验证步骤不触碰这些路径的确认。
|
|
55
|
-
- **缺失/冲突时如何 fail-closed**:任何写入落入禁止路径立即阻断;禁止路径语义不变,不得新增读取权限字段或扩大写入边界。
|
|
35
|
+
TODO
|