@tea-agent/loop-agent 0.12.0 → 0.13.0-beta.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/AGENTS.md +155 -153
- package/CHANGELOG.md +338 -265
- package/README.md +345 -298
- package/bin/agent-worker.js +22 -22
- package/bin/loop-agent.js +21 -21
- package/dist/application/dag/generate-task-dag.js +28 -28
- package/dist/application/evaluation/candidate-hash.js +75 -0
- package/dist/application/evaluation/candidate.js +52 -0
- package/dist/application/evaluation/replay.js +289 -0
- package/dist/application/evaluation/types.js +130 -0
- package/dist/cli/command-definitions.js +27 -7
- package/dist/cli/program.js +8 -4
- package/dist/commands/cursor-prompt.js +6 -6
- package/dist/commands/eval.js +235 -0
- package/dist/commands/init.js +544 -506
- package/dist/commands/knowledge.js +129 -31
- package/dist/commands/loop-benchmark.js +11 -11
- package/dist/commands/pi-reuse-benchmark.js +16 -16
- package/dist/executors/pi-sdk-executor.js +38 -24
- package/dist/executors/shell-executor.js +34 -2
- package/dist/executors/shell-presets.js +20 -0
- package/dist/executors/shell-verification.js +7 -0
- package/dist/governance/manifest-types.js +4 -0
- package/dist/infrastructure/evaluation/candidate-store.js +435 -0
- package/dist/infrastructure/evaluation/store.js +40 -0
- package/dist/sidecars/cursor-prompt/executor.js +1 -1
- package/dist/task/config-types.js +28 -1
- package/dist/task/runtime.js +27 -27
- package/dist/worker/cli.js +96 -1
- package/dist/worker/delivery/package.js +3 -3
- package/dist/worker/feature/decision-loader.js +37 -6
- package/dist/worker/feature/next-action.js +10 -2
- package/dist/worker/feature/ready-plan-projection.js +81 -0
- package/dist/worker/feature/reducer.js +2 -1
- package/dist/worker/feature/review.js +19 -2
- package/dist/worker/feature/run.js +27 -2
- package/dist/worker/follow-up/approve.js +5 -2
- package/dist/worker/follow-up/factory.js +1 -1
- package/dist/worker/observability/read-model.js +246 -41
- package/dist/worker/observe/routes.js +173 -15
- package/dist/worker/observe/spec-evidence.js +281 -0
- package/dist/worker/observe/static/api.js +46 -27
- package/dist/worker/observe/static/app.js +150 -150
- package/dist/worker/observe/static/constants.js +148 -148
- package/dist/worker/observe/static/copy.js +67 -67
- package/dist/worker/observe/static/dag-helpers.js +172 -172
- package/dist/worker/observe/static/dag-layout.d.ts +31 -31
- package/dist/worker/observe/static/dag-layout.js +83 -83
- package/dist/worker/observe/static/dag-model.js +72 -72
- package/dist/worker/observe/static/dom.js +61 -61
- package/dist/worker/observe/static/format-pool.js +67 -67
- package/dist/worker/observe/static/format.js +292 -292
- package/dist/worker/observe/static/index.html +308 -308
- package/dist/worker/observe/static/kpi.js +94 -94
- package/dist/worker/observe/static/relations.js +133 -128
- package/dist/worker/observe/static/router.js +93 -85
- package/dist/worker/observe/static/run-processing.js +148 -148
- package/dist/worker/observe/static/shell-chrome.js +68 -68
- package/dist/worker/observe/static/state.js +253 -253
- package/dist/worker/observe/static/styles.css +1902 -1890
- package/dist/worker/observe/static/views/batch.js +227 -226
- package/dist/worker/observe/static/views/dag-graph.js +172 -172
- package/dist/worker/observe/static/views/dag-inspector.js +607 -477
- package/dist/worker/observe/static/views/dag.js +362 -362
- package/dist/worker/observe/static/views/dashboard.js +445 -442
- package/dist/worker/observe/static/views/failures.js +143 -143
- package/dist/worker/observe/static/views/feature.js +492 -453
- package/dist/worker/observe/static/views/pool.js +350 -347
- package/dist/worker/observe/static/views/run.js +453 -453
- package/dist/worker/observe/static/views/session-timeline.js +205 -205
- package/dist/worker/observe/static/views/shell.js +7 -7
- package/dist/worker/observe/static/views/task.js +314 -260
- package/dist/worker/observe/static/views/timeline.js +163 -163
- package/dist/worker/pool/doctor.js +165 -0
- package/dist/worker/pool/migrate-state.js +303 -0
- package/dist/worker/pool/run-store.js +205 -17
- package/dist/worker/pool/types.js +17 -1
- package/dist/worker/pool/validation.js +100 -15
- package/dist/worker/report/morning-report.js +12 -2
- package/dist/worker/runner/run-ready.js +41 -26
- package/dist/worker/task-graph/ready-planner.js +136 -0
- package/dist/workflows/dag/backend-test-analysis-contract.js +120 -0
- package/dist/workflows/dag/canvas-observer.js +275 -275
- package/dist/workflows/dag/convergence/controller.js +16 -8
- package/dist/workflows/dag/dynamic-runtime/map.js +90 -2
- package/dist/workflows/dag/failure-routing.js +12 -1
- package/dist/workflows/dag/init-hybrid.js +2404 -360
- package/dist/workflows/dag/node-execution.js +9 -0
- package/dist/workflows/dag/prompt.js +9 -0
- package/dist/workflows/dag/report.js +35 -1
- package/dist/workflows/dag/runner.js +28 -2
- package/dist/workflows/dag/task-demand-routing.js +383 -0
- package/dist/workflows/dag/types.js +51 -13
- package/dist/workflows/dag/upstream-artifacts.js +1 -0
- package/dist/workflows/dag/validate.js +59 -1
- package/docs/README.md +106 -104
- package/docs/agent-dag-recovery-playbook.md +195 -184
- package/docs/agent-dag-runner.md +67 -67
- package/docs/architecture/README.md +26 -26
- package/docs/architecture/dag-execution.md +140 -140
- package/docs/architecture/evolution.md +54 -53
- package/docs/architecture/facts-and-state.md +71 -58
- package/docs/architecture/runtime-boundaries.md +191 -191
- package/docs/architecture/system-overview.md +93 -93
- package/docs/architecture/worker-and-feature.md +85 -81
- package/docs/cursor-prompt-sidecar.md +36 -36
- package/docs/decisions/README.md +18 -15
- package/docs/design/README.md +167 -77
- package/docs/development-principles.md +73 -73
- package/docs/exec-plans/README.md +6 -6
- package/docs/exec-plans/active/README.md +15 -9
- package/docs/exec-plans/completed/README.md +85 -73
- package/docs/feature-workflow.md +389 -261
- package/docs/harness-methodology-debugging.md +153 -153
- package/docs/harness-methodology-tdd.md +130 -130
- package/docs/harness-methodology-verification.md +27 -27
- package/docs/init-surface.manifest.json +289 -280
- package/docs/loop-agent-harness.md +142 -130
- package/docs/production-readiness.md +96 -96
- package/docs/progress/README.md +64 -54
- package/docs/reports/README.md +117 -94
- package/docs/skills/README.md +7 -7
- package/docs/skills/vetted-skill-registry.md +29 -27
- package/docs/templates/adr.md +60 -60
- package/docs/templates/agent-dag-authority-surface-audit.prompt.md +94 -94
- package/docs/templates/agent-dag-decision-envelope.schema.json +213 -213
- package/docs/templates/agent-dag-decision-gate-dogfood-report.md +117 -117
- package/docs/templates/agent-dag-decision-gate.prompt.md +246 -246
- package/docs/templates/agent-dag-process-supervisor.prompt.md +98 -98
- package/docs/templates/agent-dag-report.schema.json +473 -473
- package/docs/templates/agent-dag-review-verdict.prompt.md +68 -68
- package/docs/templates/agent-dag.base.json +190 -190
- package/docs/templates/agent-dag.final-verification.json +185 -185
- package/docs/templates/agent-dag.schema.json +411 -383
- package/docs/templates/agent-dag.supervised-implementation.json +501 -501
- package/docs/templates/backend-test-analysis.schema.json +44 -0
- package/docs/templates/backend-test-dag.generate-pytest.prompt.md +202 -139
- package/docs/templates/backend-test-dag.json +311 -276
- package/docs/templates/backend-test-dag.retrospect.prompt.md +125 -125
- package/docs/templates/backend-test-dag.review-cases.prompt.md +81 -81
- package/docs/templates/exec-plan.md +64 -64
- package/docs/templates/feature-spec.md +53 -53
- package/docs/templates/frontend-design-contract.md +42 -33
- package/docs/templates/frontend-task-constraints.md +35 -25
- package/docs/templates/frontend-task-requirement.md +70 -61
- package/docs/templates/frontend-test-dag.generate-cases.prompt.md +5 -0
- package/docs/templates/frontend-test-dag.json +23 -0
- package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +3 -0
- package/docs/templates/frontend-test-dag.retrospect.prompt.md +3 -0
- package/docs/templates/frontend-test-dag.review-cases.prompt.md +3 -0
- package/docs/templates/frontend-test-dag.review-execution.prompt.md +3 -0
- package/docs/templates/harness.schema.json +221 -221
- package/docs/templates/hybrid-dag.json +188 -188
- package/docs/templates/init-evolution-review.md +35 -35
- package/docs/templates/interactive-ui-round2-experiment.md +66 -66
- package/docs/templates/knowledge-graph-bootstrap-dag.json +118 -0
- package/docs/templates/knowledge-sync-dag.json +178 -0
- package/docs/templates/knowledge-sync-draft.schema.json +71 -0
- package/docs/templates/product-line/AGENTS.md +8 -8
- package/docs/templates/product-line/README.md +9 -9
- package/docs/templates/product-line/acceptance.yaml +14 -14
- package/docs/templates/product-line/closeout.yaml +9 -9
- package/docs/templates/product-line/design.md +13 -13
- package/docs/templates/product-line/links.md +10 -10
- package/docs/templates/product-line/requirement.md +17 -17
- package/docs/templates/product-line/task-graph.yaml +15 -15
- package/docs/templates/product-line/task.yaml +64 -64
- package/docs/templates/product-line/test-plan.md +7 -7
- package/docs/templates/production-readiness-checklist.md +57 -57
- package/docs/templates/progress-log.md +17 -17
- package/docs/templates/project-start-checklist.md +9 -9
- package/docs/templates/qa-report.md +48 -48
- package/docs/templates/sprint-contract.md +29 -29
- package/docs/templates/worker-dogfood-evidence.md +80 -80
- package/docs/templates/worker-dogfood-setup.md +68 -68
- package/docs/verification-matrix.md +70 -66
- package/examples/decision-gate-agent-dag.json +177 -177
- package/examples/example-dag.json +46 -46
- package/examples/hybrid-loop-agent-dag.json +189 -189
- package/harness.json +66 -66
- package/package.json +88 -46
- package/scripts/check-product-line-docs.sh +29 -29
- package/scripts/check-task-pool-root.sh +32 -32
- package/scripts/kb-bootstrap-init-skeleton.sh +240 -0
- package/scripts/kb-graph-incremental-prepare.mjs +386 -0
- package/scripts/kb-graph-incremental-prepare.sh +5 -0
- package/scripts/kb-graph-materialize.mjs +105 -0
- package/scripts/kb-graph-materialize.sh +4 -0
- package/scripts/kb-graph-promote.mjs +164 -0
- package/scripts/kb-graph-promote.sh +4 -0
- package/scripts/kb-query.mjs +554 -0
- package/scripts/kb-query.sh +5 -0
- package/skills/agent-worker/SKILL.md +39 -37
- package/skills/agent-worker/references/agent-worker-operator.md +60 -43
- package/skills/ai-engineering-context/SKILL.md +48 -48
- package/skills/analyze-product-dependencies/SKILL.md +67 -0
- package/skills/analyze-product-dependencies/agents/openai.yaml +4 -0
- package/skills/analyze-product-dependencies/references/api-documentation-schema.md +30 -0
- package/skills/analyze-product-dependencies/references/dependency-analysis-schema.md +28 -0
- package/skills/analyze-product-dependencies/references/example.md +76 -0
- package/skills/analyze-product-dependencies/references/forward-test-cases.md +35 -0
- package/skills/analyze-product-dependencies/references/input-contract.md +11 -0
- package/skills/analyze-product-dependencies/references/scouting-rules.md +61 -0
- package/skills/analyze-product-dependencies/scripts/test-validators.mjs +267 -0
- package/skills/analyze-product-dependencies/scripts/validate-api-documentation.mjs +101 -0
- package/skills/analyze-product-dependencies/scripts/validate-dependency-analysis.mjs +142 -0
- package/skills/analyze-product-dependencies/scripts/validate-product-requirement-input.mjs +76 -0
- package/skills/analyze-product-dependencies/scripts/validation-helpers.mjs +146 -0
- package/skills/analyze-product-requirements/SKILL.md +90 -0
- package/skills/analyze-product-requirements/agents/openai.yaml +4 -0
- package/skills/analyze-product-requirements/references/acceptance-criteria.md +91 -0
- package/skills/analyze-product-requirements/references/clarification-and-knowledge.md +56 -0
- package/skills/analyze-product-requirements/references/example.md +86 -0
- package/skills/analyze-product-requirements/references/forward-test-cases.md +66 -0
- package/skills/analyze-product-requirements/references/product-analysis-schema.md +32 -0
- package/skills/analyze-product-requirements/references/product-requirement-schema.md +33 -0
- package/skills/analyze-product-requirements/references/requirement-clarification-schema.md +35 -0
- package/skills/analyze-product-requirements/scripts/test-validators.mjs +193 -0
- package/skills/analyze-product-requirements/scripts/validate-product-analysis.mjs +69 -0
- package/skills/analyze-product-requirements/scripts/validate-product-requirement.mjs +97 -0
- package/skills/analyze-product-requirements/scripts/validate-requirement-clarification.mjs +98 -0
- package/skills/analyze-product-requirements/scripts/validation-helpers.mjs +156 -0
- package/skills/code-review-core/SKILL.md +20 -20
- package/skills/codebase-scout/SKILL.md +19 -19
- package/skills/frontend-design-review/SKILL.md +66 -59
- package/skills/frontend-design-review/references/review-checklist.md +58 -37
- package/skills/frontend-implementation/SKILL.md +47 -51
- package/skills/frontend-implementation/references/code-standards.md +32 -34
- package/skills/frontend-implementation/references/design-spec.md +46 -46
- package/skills/frontend-implementation/references/node-contracts.md +76 -32
- package/skills/frontend-review/SKILL.md +59 -53
- package/skills/frontend-review/references/review-findings.md +47 -42
- package/skills/frontend-verification/SKILL.md +53 -40
- package/skills/frontend-verification/references/verification-checklist.md +68 -56
- package/skills/grill-me/SKILL.md +10 -10
- package/skills/grill-with-docs/SKILL.md +88 -88
- package/skills/grill-with-docs/adr-format.md +47 -47
- package/skills/grill-with-docs/context-format.md +60 -60
- package/skills/init-capability-evolution/SKILL.md +70 -70
- package/skills/loop-agent/SKILL.md +151 -151
- package/skills/loop-agent/references/README.md +67 -67
- package/skills/loop-agent/references/command-reference.md +505 -452
- package/skills/loop-agent/references/docs-converge.md +126 -126
- package/skills/loop-agent/references/harness-policy.md +263 -263
- package/skills/loop-agent/references/hybrid-dag.md +238 -233
- package/skills/loop-agent/references/learned/README.md +21 -21
- package/skills/loop-agent/references/long-running-loop.md +57 -57
- package/skills/loop-agent/references/model-routing.md +36 -36
- package/skills/loop-agent/references/multi-worktree.md +54 -54
- package/skills/loop-agent/references/one-shot-runs.md +85 -85
- package/skills/loop-agent/references/orchestrator-and-interventions.md +169 -169
- package/skills/loop-agent/references/pi-prompt.md +23 -23
- package/skills/loop-agent/references/pi-subagent-assisted-mode.md +84 -84
- package/skills/loop-agent/references/post-implementation-and-patterns.md +44 -44
- package/skills/loop-agent/references/task-workflow.md +89 -89
- package/skills/loop-agent/references/verification-and-failure-handling.md +139 -139
- package/skills/playwright-cli/SKILL.md +420 -0
- package/skills/playwright-cli/references/element-attributes.md +23 -0
- package/skills/playwright-cli/references/playwright-tests.md +39 -0
- package/skills/playwright-cli/references/request-mocking.md +87 -0
- package/skills/playwright-cli/references/running-code.md +241 -0
- package/skills/playwright-cli/references/session-management.md +225 -0
- package/skills/playwright-cli/references/storage-state.md +275 -0
- package/skills/playwright-cli/references/test-generation.md +433 -0
- package/skills/playwright-cli/references/tracing.md +139 -0
- package/skills/playwright-cli/references/video-recording.md +143 -0
- package/skills/playwright-cli-case-generator/SKILL.md +74 -0
- package/skills/requesting-code-review/SKILL.md +101 -101
- package/skills/requesting-code-review/code-reviewer.md +168 -168
- package/skills/systematic-debugging/CREATION-LOG.md +119 -119
- package/skills/systematic-debugging/SKILL.md +296 -296
- package/skills/systematic-debugging/condition-based-waiting-example.ts +158 -158
- package/skills/systematic-debugging/condition-based-waiting.md +115 -115
- package/skills/systematic-debugging/defense-in-depth.md +122 -122
- package/skills/systematic-debugging/find-polluter.sh +63 -63
- package/skills/systematic-debugging/root-cause-tracing.md +169 -169
- package/skills/systematic-debugging/test-academic.md +14 -14
- package/skills/systematic-debugging/test-pressure-1.md +58 -58
- package/skills/systematic-debugging/test-pressure-2.md +68 -68
- package/skills/systematic-debugging/test-pressure-3.md +69 -69
- package/skills/test-driven-development/SKILL.md +20 -20
- package/skills/using-git-worktrees/SKILL.md +215 -215
- package/skills/verification-before-completion/SKILL.md +154 -154
- package/skills/webapp-testing/SKILL.md +19 -19
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
1
3
|
import path from "node:path";
|
|
2
4
|
import { recordDecisionEnvelopeForNode, shouldPauseOnHumanEscalation, writeHumanEscalationArtifacts, } from "./decision-envelope.js";
|
|
3
5
|
import { writeNodeRecord, writeNodeSkillArtifacts } from "./run-store.js";
|
|
@@ -405,6 +407,13 @@ export async function executeDagNode(input) {
|
|
|
405
407
|
assistantText: node.assistantText,
|
|
406
408
|
});
|
|
407
409
|
Object.assign(node, artifactMeta);
|
|
410
|
+
if (task.shell?.jsonArtifactGate) {
|
|
411
|
+
const artifactPath = path.join(runDir, task.shell.jsonArtifactGate.outputDir, task.shell.jsonArtifactGate.artifactName);
|
|
412
|
+
const bytes = await readFile(artifactPath);
|
|
413
|
+
node.structuredArtifactPath = artifactPath;
|
|
414
|
+
node.structuredArtifactSha256 = createHash("sha256").update(bytes).digest("hex");
|
|
415
|
+
node.structuredArtifactSchemaId = task.shell.jsonArtifactGate.schemaId;
|
|
416
|
+
}
|
|
408
417
|
}
|
|
409
418
|
state.nodes[nodeId].nodeRecordPath = path.join(runDir, `${nodeId}.json`);
|
|
410
419
|
await writeNodeRecord(runDir, nodeId, state.nodes[nodeId]);
|
|
@@ -73,6 +73,15 @@ export function buildUpstreamContext(task, upstream, maxChars = MAX_UPSTREAM_CHA
|
|
|
73
73
|
: record.assistantArtifactPath;
|
|
74
74
|
const { preview, truncated } = formatStdoutPreview(upstreamText, maxChars);
|
|
75
75
|
let section = `## Upstream output: ${depId}\n${preview}`;
|
|
76
|
+
if (record.structuredArtifactPath) {
|
|
77
|
+
section += [
|
|
78
|
+
"",
|
|
79
|
+
"Validated structured artifact (read-only runner evidence — use read tool to fetch; do not edit):",
|
|
80
|
+
`- path: ${record.structuredArtifactPath}`,
|
|
81
|
+
`- schema: ${record.structuredArtifactSchemaId ?? "unknown"}`,
|
|
82
|
+
`- sha256: ${record.structuredArtifactSha256 ?? "unknown"}`,
|
|
83
|
+
].join("\n");
|
|
84
|
+
}
|
|
76
85
|
if (truncated && artifactPath) {
|
|
77
86
|
section += formatUpstreamArtifactPointerMap(record, artifactKind);
|
|
78
87
|
}
|
|
@@ -186,6 +186,20 @@ const dagRunReportEntrySchema = z
|
|
|
186
186
|
runtimeContractReason: z.string().optional(),
|
|
187
187
|
})
|
|
188
188
|
.optional(),
|
|
189
|
+
evaluationAssociation: z.discriminatedUnion("status", [
|
|
190
|
+
z.object({ status: z.literal("absent-legacy") }).strict(),
|
|
191
|
+
z
|
|
192
|
+
.object({
|
|
193
|
+
status: z.literal("present"),
|
|
194
|
+
campaignId: z.string().min(1),
|
|
195
|
+
candidateId: z.string().min(1),
|
|
196
|
+
bundleHash: z.string().regex(/^sha256:[a-f0-9]{64}$/),
|
|
197
|
+
seed: z.number().int().nonnegative(),
|
|
198
|
+
split: z.enum(["public", "private", "held_out"]).optional(),
|
|
199
|
+
taskRef: z.string().min(1).optional(),
|
|
200
|
+
})
|
|
201
|
+
.strict(),
|
|
202
|
+
]),
|
|
189
203
|
executorJsonl: dagArtifactRefSchema,
|
|
190
204
|
nodes: z.array(dagNodeReportRowSchema),
|
|
191
205
|
})
|
|
@@ -502,6 +516,9 @@ export async function buildDagRunReportEntry(input) {
|
|
|
502
516
|
pausedByNodeId: input.state.pausedByNodeId,
|
|
503
517
|
pauseReason: input.state.pauseReason,
|
|
504
518
|
...summarizeControllerIdentityForReport(input.state.controllerIdentityRef, input.spec),
|
|
519
|
+
evaluationAssociation: input.state.evaluation
|
|
520
|
+
? { status: "present", ...input.state.evaluation }
|
|
521
|
+
: { status: "absent-legacy" },
|
|
505
522
|
nodes,
|
|
506
523
|
};
|
|
507
524
|
const primaryNode = resolvePrimaryFailureNode(partialEntry);
|
|
@@ -757,7 +774,9 @@ function formatConvergenceSummary(run) {
|
|
|
757
774
|
`- convergence: enabled=${convergence.enabled}, pass=${convergence.currentPass}/${convergence.maxPasses}, terminal=${convergence.terminalReason ?? "-"}`,
|
|
758
775
|
];
|
|
759
776
|
for (const pass of convergence.passHistory) {
|
|
760
|
-
const verifySummary = pass.verifyPhase ||
|
|
777
|
+
const verifySummary = pass.verifyPhase ||
|
|
778
|
+
pass.verifyQuota ||
|
|
779
|
+
pass.verifyCommandCount !== undefined
|
|
761
780
|
? `, verify=${pass.verifyPhase ?? "-"} quota=${pass.verifyQuota ?? "-"} commands=${pass.verifyCommandCount ?? "-"}`
|
|
762
781
|
: "";
|
|
763
782
|
lines.push(`- pass ${pass.pass}: ${pass.status}, reason=${pass.reason}, hardVerify=${pass.hardVerifyStatus ?? "-"}(${pass.hardVerifyFailureCategory ?? "-"})${verifySummary}, artifacts=${pass.artifactRefs.length}`);
|
|
@@ -809,6 +828,21 @@ export function formatDagReportMarkdown(report) {
|
|
|
809
828
|
`- runDir: ${run.runDir}`,
|
|
810
829
|
`- startedAt: ${run.startedAt}`,
|
|
811
830
|
`- finishedAt: ${run.finishedAt ?? "-"}`,
|
|
831
|
+
...(run.evaluationAssociation.status === "present"
|
|
832
|
+
? [
|
|
833
|
+
`- evaluationAssociation: present`,
|
|
834
|
+
`- campaignId: ${run.evaluationAssociation.campaignId}`,
|
|
835
|
+
`- candidateId: ${run.evaluationAssociation.candidateId}`,
|
|
836
|
+
`- bundleHash: ${run.evaluationAssociation.bundleHash}`,
|
|
837
|
+
`- seed: ${run.evaluationAssociation.seed}`,
|
|
838
|
+
...(run.evaluationAssociation.split
|
|
839
|
+
? [`- split: ${run.evaluationAssociation.split}`]
|
|
840
|
+
: []),
|
|
841
|
+
...(run.evaluationAssociation.taskRef
|
|
842
|
+
? [`- taskRef: ${run.evaluationAssociation.taskRef}`]
|
|
843
|
+
: []),
|
|
844
|
+
]
|
|
845
|
+
: ["- evaluationAssociation: absent/legacy"]),
|
|
812
846
|
`- executorJsonl: ${run.executorJsonl.path} (${run.executorJsonl.exists ? "exists" : "missing"})`,
|
|
813
847
|
...formatConvergenceSummary(run),
|
|
814
848
|
"",
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readdir, readFile } from "node:fs/promises";
|
|
2
2
|
import { hostname } from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { readCandidateRecord } from "../../infrastructure/evaluation/candidate-store.js";
|
|
4
5
|
import { CANONICAL_TASK_ID_PATTERN, formatLocalCompactDate, } from "../../task/runtime.js";
|
|
5
6
|
import { getDagRunDir, isTerminalDagRunStatus, locateDagRun, readHumanApprovalArtifact, requireActiveDagRun, } from "./lifecycle.js";
|
|
6
7
|
import { moveToCompletedRunDir, moveToPausedRunDir, prepareActiveRunDir, writeRunSpec, writeRunState, } from "./run-store.js";
|
|
@@ -15,7 +16,7 @@ import { relocateConvergenceArtifactPaths, relocateRunArtifactPaths, } from "./u
|
|
|
15
16
|
import { createSkillSnapshot, prepareSkillSnapshotForContinuation, writeSkillSnapshot, } from "./skill-snapshot.js";
|
|
16
17
|
import { buildNodePrompt, buildNodePromptWithResolvedSkillInstructions, executeDagNode, } from "./node-execution.js";
|
|
17
18
|
import { runConvergencePassController, shouldEnableDagConvergence, } from "./convergence/controller.js";
|
|
18
|
-
import { executeDagRanksOnce, isConditionSkippedReason
|
|
19
|
+
import { executeDagRanksOnce, isConditionSkippedReason } from "./scheduler.js";
|
|
19
20
|
import { topoSortToRanks } from "./topo.js";
|
|
20
21
|
import { parseDagSpec, resolveModelForTask, } from "./types.js";
|
|
21
22
|
import { executeDynamicCondition } from "./dynamic-runtime/condition.js";
|
|
@@ -134,6 +135,7 @@ export function createInitialRunState(spec, opts, ranks, runId = opts.runId ?? "
|
|
|
134
135
|
status: opts.initOnly || opts.dryRun ? "pending" : "running",
|
|
135
136
|
ranks,
|
|
136
137
|
nodes,
|
|
138
|
+
...(spec.evaluation ? { evaluation: { ...spec.evaluation } } : {}),
|
|
137
139
|
...(shouldEnableDagConvergence(spec)
|
|
138
140
|
? {
|
|
139
141
|
convergence: {
|
|
@@ -146,9 +148,32 @@ export function createInitialRunState(spec, opts, ranks, runId = opts.runId ?? "
|
|
|
146
148
|
: {}),
|
|
147
149
|
};
|
|
148
150
|
}
|
|
151
|
+
export function assertFrozenEvaluationBinding(spec, state) {
|
|
152
|
+
const declared = spec.evaluation;
|
|
153
|
+
const frozen = state.evaluation;
|
|
154
|
+
if (!declared && !frozen)
|
|
155
|
+
return;
|
|
156
|
+
if (!declared ||
|
|
157
|
+
!frozen ||
|
|
158
|
+
JSON.stringify(declared) !== JSON.stringify(frozen)) {
|
|
159
|
+
throw new Error(`dag run ${state.runId} evaluation binding drifted between run.json and state.json; refuse to continue`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
export async function assertEvaluationBindingPreflight(cwd, binding) {
|
|
163
|
+
if (!binding)
|
|
164
|
+
return;
|
|
165
|
+
const record = await readCandidateRecord(cwd, binding.candidateId);
|
|
166
|
+
if (record.manifest.bundleHash !== binding.bundleHash) {
|
|
167
|
+
throw new Error(`evaluation bundleHash mismatch for ${binding.candidateId}: declared ${binding.bundleHash}, registry ${record.manifest.bundleHash}`);
|
|
168
|
+
}
|
|
169
|
+
if (!["eligible", "experimenting", "accepted"].includes(record.status)) {
|
|
170
|
+
throw new Error(`evaluation candidate ${binding.candidateId} lifecycle status ${record.status} is not executable`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
149
173
|
export async function runDag(spec, opts) {
|
|
150
174
|
assertValidDagSpec(spec);
|
|
151
|
-
//
|
|
175
|
+
// Candidate identity is validated before run-id allocation or directory creation.
|
|
176
|
+
await assertEvaluationBindingPreflight(opts.cwd, spec.evaluation);
|
|
152
177
|
const runningIdentity = resolveRunningControllerIdentity();
|
|
153
178
|
if (!runningIdentity) {
|
|
154
179
|
throw new Error("running controller identity could not be resolved; refuse to create an unpinned DAG run");
|
|
@@ -242,6 +267,7 @@ export async function resumeDagRun(opts) {
|
|
|
242
267
|
if (isTerminalDagRunStatus(state.status)) {
|
|
243
268
|
throw new Error(`dag run ${opts.runId} is already terminal (status=${state.status})`);
|
|
244
269
|
}
|
|
270
|
+
assertFrozenEvaluationBinding(spec, state);
|
|
245
271
|
// Runtime contract + controller identity must be re-verified before executing
|
|
246
272
|
// any remaining node on resume; drift fails closed.
|
|
247
273
|
const runningIdentity = resolveRunningControllerIdentity();
|
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
const OUT_OF_SCOPE_HEADING = /^(?:非目标|不包含|范围外|不在范围|out\s+of\s+scope|non[-\s]?goals?)(?:\s*(?:[((].*[))]|[::].*|[/—–-]\s*.*))?$/i;
|
|
2
|
+
const FRONTEND_TASK_TYPE = /^\s*(?:[-*]\s*)?task\s+type\s*:\s*frontend-feature\s*$/im;
|
|
3
|
+
const BACKEND_TASK_TYPE = /^\s*(?:[-*]\s*)?task\s+type\s*:\s*backend-feature\s*$/im;
|
|
4
|
+
const FRONTEND_NEGATION_PATTERNS = [
|
|
5
|
+
/(?:不涉及|无需|不需要|不包含|不修改|不实现|不交付|不要|禁止|不得|排除).{0,16}(?:前端(?:页面|组件|界面|功能|交互)?|页面|组件|界面|用户界面|浏览器交互)/,
|
|
6
|
+
/(?:前端(?:页面|组件|界面|功能|交互)?|页面|组件|界面|用户界面).{0,12}(?:不在(?:本次)?范围|无需|不需要|不修改|不实现|不交付|不要|禁止|不得)/,
|
|
7
|
+
/(?:不涉及|无需|不需要|不包含|不修改|不实现|不交付|不要|禁止|不得|排除).{0,16}(?<![A-Za-z0-9_])UI(?![A-Za-z0-9_])/i,
|
|
8
|
+
/(?<![A-Za-z0-9_])UI(?![A-Za-z0-9_]).{0,12}(?:不在(?:本次)?范围|无需|不需要|不修改|不实现|不交付|不要|禁止|不得)/i,
|
|
9
|
+
/\b(?:backend|server[-\s]?side)\s+only\b/i,
|
|
10
|
+
/\b(?:no|without)\s+(?:frontend|front[-\s]?end|ui|user\s+interface)\b/i,
|
|
11
|
+
/\b(?:do\s+not|don't|must\s+not|should\s+not)\s+(?:(?:add|implement|build|create|update|modify|fix|refactor|render|style|change|touch)\s+)?(?:(?:the|any|a|an)\s+)?(?:frontend|front[-\s]?end|ui|user\s+interface|page|component|form|modal|dialog)\b/i,
|
|
12
|
+
/\bwithout\s+(?:(?:adding|implementing|building|creating|updating|modifying|fixing|refactoring|rendering|styling|changing|touching)\s+)?(?:(?:the|any|a|an)\s+)?(?:frontend|front[-\s]?end|ui|user\s+interface|page|component|form|modal|dialog)\b/i,
|
|
13
|
+
/\b(?:frontend|front[-\s]?end|ui|user\s+interface|page|component|form|modal|dialog)\b[^.?!;\n]{0,20}\b(?:is|are)?\s*(?:out\s+of\s+scope|excluded|not\s+(?:required|needed)|unchanged)\b/i,
|
|
14
|
+
];
|
|
15
|
+
const FRONTEND_DELIVERY_PATTERNS = [
|
|
16
|
+
/(?:新增|实现|开发|修改|重构|修复|优化|创建|渲染|搭建|改造)[^。!?;,,\n]{0,24}(?:前端(?:页面|组件|界面|功能|交互)|页面|组件|界面|表单|弹窗|对话框|用户界面|浏览器(?:页面|交互|行为))/,
|
|
17
|
+
/(?:前端(?:页面|组件|界面|功能|交互)|页面|组件|界面|表单|弹窗|对话框|用户界面|浏览器交互)[^。!?;,,\n]{0,16}(?:新增|实现|开发|修改|重构|修复|优化|创建|渲染|搭建|改造)/,
|
|
18
|
+
/\b(?:add|implement|build|create|update|modify|fix|refactor|render|style)\b[^.?!;\n]{0,48}\b(?:front[-\s]?end\s+(?:page|component|view|form|modal|interaction|ui)|ui|form|modal|dialog|dashboard|navigation|toast|button|dropdown|user\s+interface|browser\s+interaction)\b/i,
|
|
19
|
+
];
|
|
20
|
+
const PATH_SUPPORTED_FRONTEND_DELIVERY_PATTERNS = [
|
|
21
|
+
/(?:新增|实现|开发|修改|重构|修复|优化|创建|渲染|搭建|改造)[^。!?;,,\n]{0,24}(?:页(?!\p{Script=Han})|表格|列表|按钮|导航|菜单|标签页|下拉框|选择器|提示|通知|加载状态|空状态|错误状态|交互|样式|布局)/u,
|
|
22
|
+
/(?:新增|实现|开发|修改|重构|修复|优化|创建|渲染|搭建|改造)[^。!?;,,\n]{0,24}(?<![A-Za-z0-9_])(?:DOM|UI|React|Vue|Svelte|Angular)(?![A-Za-z0-9_])/i,
|
|
23
|
+
/\b(?:add|implement|build|create|update|modify|fix|refactor|render|style)\b[^.?!;\n]{0,48}\b(?:page|component|screen|layout|dom|react|vue|svelte|angular)\b/i,
|
|
24
|
+
];
|
|
25
|
+
const BACKEND_DELIVERY_PATTERNS = [
|
|
26
|
+
/(?:新增|实现|开发|修改|重构|修复|提供|创建|调整|优化|迁移|扩展)[^。!?;,,\n]{0,24}(?:后端(?:服务|接口|逻辑|API)?|服务端(?:服务|接口|逻辑)?|接口|数据库|数据表(?!格)|数据库索引|索引|控制器|服务层|仓储层)/i,
|
|
27
|
+
/(?:后端(?:服务|接口|逻辑|API)?|服务端(?:服务|接口|逻辑)?|接口|数据库|数据表(?!格)|数据库索引|控制器|服务层|仓储层)[^。!?;,,\n]{0,16}(?:新增|实现|开发|修改|重构|修复|提供|创建|调整|优化|迁移|扩展)/i,
|
|
28
|
+
/(?:新增|实现|开发|修改|重构|修复|提供|创建|调整|优化|迁移|扩展)[^。!?;,,\n]{0,24}(?<![A-Za-z0-9_])API(?![A-Za-z0-9_])/i,
|
|
29
|
+
/(?<![A-Za-z0-9_])API(?![A-Za-z0-9_])[^。!?;,,\n]{0,16}(?:新增|实现|开发|修改|重构|修复|提供|创建|调整|优化|迁移|扩展)/i,
|
|
30
|
+
/\b(?:add|implement|build|create|update|modify|fix|refactor|migrate|extend)\b[^.?!;\n]{0,48}\b(?:backend|server[-\s]?side|api|endpoint|database|migration|controller|service|repository)\b/i,
|
|
31
|
+
];
|
|
32
|
+
const BACKEND_NEGATION_PATTERNS = [
|
|
33
|
+
/(?:不涉及|无需|不需要|不包含|不修改|不实现|不交付|不要|禁止|不得|排除).{0,16}(?:后端|服务端|接口|数据库|数据表(?!格)|索引)/,
|
|
34
|
+
/(?:不涉及|无需|不需要|不包含|不修改|不实现|不交付|不要|禁止|不得|排除).{0,16}(?<![A-Za-z0-9_])API(?![A-Za-z0-9_])/i,
|
|
35
|
+
/\b(?:no|without)\s+(?:backend|server[-\s]?side|api|endpoint|database)\b/i,
|
|
36
|
+
/\b(?:do\s+not|don't|must\s+not|should\s+not)\s+(?:(?:add|implement|build|create|update|modify|fix|refactor|migrate|change|touch)\s+)?(?:(?:the|any|a|an)\s+)?(?:backend|server[-\s]?side|api|endpoint|database)\b/i,
|
|
37
|
+
];
|
|
38
|
+
const FRONTEND_PATH_PATTERNS = [
|
|
39
|
+
/(?:^|\/)apps\/web\/src(?:\/|$)/i,
|
|
40
|
+
/(?:^|\/)(?:frontend|client)(?:\/|$)/i,
|
|
41
|
+
/(?:^|\/)src\/(?:pages?|components?|views?)(?:\/|$)/i,
|
|
42
|
+
/\.(?:tsx|jsx|vue|svelte|css|scss|less)(?:$|[*?])/i,
|
|
43
|
+
];
|
|
44
|
+
const BACKEND_PATH_PATTERNS = [
|
|
45
|
+
/(?:^|\/)(?:backend|server|api|controllers?|services?|repositories?|db|database|migrations?)(?:\/|$)/i,
|
|
46
|
+
/\.(?:sql)(?:$|[*?])/i,
|
|
47
|
+
];
|
|
48
|
+
const NON_PRODUCT_PATH_SEGMENT = /(?:^|\/)(?:docs?|skills?|tests?|__tests__|e2e|cypress)(?:\/|$)/i;
|
|
49
|
+
const FRONTEND_PATH_EXCLUSIONS = NON_PRODUCT_PATH_SEGMENT;
|
|
50
|
+
const DOCUMENTATION_ONLY_MARKERS = /(?:文档|说明|指南|规范|测试用例|测试计划|评审|审查|\b(?:docs?|documentation|guide|specification|test\s+cases?|test\s+plan|review)\b)/i;
|
|
51
|
+
const DOCUMENTATION_ONLY_ACTIONS = /(?:新增|实现|开发|创建|编写|更新|修改|重构|修复|优化|渲染|搭建|改造|补充|生成|评审|审查|\b(?:add|implement|build|create|write|update|modify|refactor|fix|optimize|render|style|document|review|generate)\b)/i;
|
|
52
|
+
const PRODUCT_AND_DOCUMENT_DELIVERY = /(?:新增|实现|开发|修改|重构|修复|优化|创建|渲染|搭建|改造)[^。!?;,,\n]{0,24}(?:前端|页面|组件|界面|表单|交互|样式|布局|DOM|UI|React|Vue|Svelte|Angular|源码|代码)[^。!?;,,\n]{0,16}(?:并|同时|以及|和|与|\band\b)[^。!?;,,\n]{0,16}(?:文档|说明|指南|规范|\b(?:docs?|documentation|guide)\b)/i;
|
|
53
|
+
const TEST_ONLY_MARKERS = /(?:测试|用例|\b(?:e2e|unit|integration|component)\s+tests?\b|\btests?\b)/i;
|
|
54
|
+
const PRODUCT_AND_TEST_DELIVERY = /(?:并|同时|以及|和|与|\band\b).{0,12}(?:添加|新增|编写|补充|测试|\b(?:add|write|test)\b)/i;
|
|
55
|
+
const NON_PRODUCT_ALLOWED_PATH_PATTERNS = [
|
|
56
|
+
NON_PRODUCT_PATH_SEGMENT,
|
|
57
|
+
/\.(?:test|spec)\.[^/]+$/i,
|
|
58
|
+
/\.cy\.[^/]+$/i,
|
|
59
|
+
/\.mdx?(?:$|[*?])/i,
|
|
60
|
+
];
|
|
61
|
+
const SPECIAL_TEMPLATE_BY_TASK_KIND = {
|
|
62
|
+
"frontend-implementation": "frontend-implementation",
|
|
63
|
+
"frontend-test": "frontend-test-dag",
|
|
64
|
+
"backend-test": "backend-test-dag",
|
|
65
|
+
"knowledge-sync": "knowledge-sync-dag",
|
|
66
|
+
"knowledge-graph-bootstrap": "knowledge-graph-bootstrap-dag",
|
|
67
|
+
};
|
|
68
|
+
const TASK_KIND_ROUTING_REASON = {
|
|
69
|
+
"frontend-implementation": 'taskKind "frontend-implementation" selects the dedicated frontend DAG template',
|
|
70
|
+
"frontend-test": 'taskKind "frontend-test" selects the dedicated frontend test DAG template',
|
|
71
|
+
"backend-test": 'taskKind "backend-test" selects the dedicated backend test DAG template',
|
|
72
|
+
"knowledge-sync": 'taskKind "knowledge-sync" selects the dedicated knowledge-sync DAG template',
|
|
73
|
+
"knowledge-graph-bootstrap": 'taskKind "knowledge-graph-bootstrap" selects the dedicated knowledge-graph bootstrap DAG template',
|
|
74
|
+
};
|
|
75
|
+
const SPECIAL_REQUESTED_TEMPLATES = new Set([
|
|
76
|
+
"frontend-implementation",
|
|
77
|
+
"frontend-test-dag",
|
|
78
|
+
"backend-test-dag",
|
|
79
|
+
"knowledge-sync-dag",
|
|
80
|
+
"knowledge-graph-bootstrap-dag",
|
|
81
|
+
]);
|
|
82
|
+
function stripOutOfScopeSections(markdown) {
|
|
83
|
+
const output = [];
|
|
84
|
+
let skippedHeadingLevel = null;
|
|
85
|
+
for (const line of markdown.split(/\r?\n/)) {
|
|
86
|
+
const heading = /^(#{1,6})\s+(.+?)\s*$/.exec(line.trim());
|
|
87
|
+
if (heading) {
|
|
88
|
+
const level = heading[1].length;
|
|
89
|
+
if (skippedHeadingLevel !== null && level <= skippedHeadingLevel) {
|
|
90
|
+
skippedHeadingLevel = null;
|
|
91
|
+
}
|
|
92
|
+
if (OUT_OF_SCOPE_HEADING.test(heading[2].trim())) {
|
|
93
|
+
skippedHeadingLevel = level;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (skippedHeadingLevel === null)
|
|
98
|
+
output.push(line);
|
|
99
|
+
}
|
|
100
|
+
return output.join("\n");
|
|
101
|
+
}
|
|
102
|
+
function stripFencedCodeBlocks(markdown) {
|
|
103
|
+
const output = [];
|
|
104
|
+
let fenceCharacter = null;
|
|
105
|
+
for (const line of markdown.split(/\r?\n/)) {
|
|
106
|
+
const fence = /^\s*(`{3,}|~{3,})/.exec(line);
|
|
107
|
+
if (fence) {
|
|
108
|
+
const character = fence[1][0];
|
|
109
|
+
if (fenceCharacter === null)
|
|
110
|
+
fenceCharacter = character;
|
|
111
|
+
else if (fenceCharacter === character)
|
|
112
|
+
fenceCharacter = null;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (fenceCharacter === null)
|
|
116
|
+
output.push(line);
|
|
117
|
+
}
|
|
118
|
+
return output.join("\n");
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Normalize task prose before deterministic demand/capability signal checks.
|
|
122
|
+
* Examples and explicit non-goals must not become positive routing evidence.
|
|
123
|
+
*/
|
|
124
|
+
export function normalizeTaskRequirementText(markdown) {
|
|
125
|
+
return stripOutOfScopeSections(stripFencedCodeBlocks(markdown));
|
|
126
|
+
}
|
|
127
|
+
function splitClauses(text) {
|
|
128
|
+
return text
|
|
129
|
+
.split(/[。!?!?;;,,\r\n]+/)
|
|
130
|
+
.map((clause) => clause.trim())
|
|
131
|
+
.filter(Boolean);
|
|
132
|
+
}
|
|
133
|
+
function matchesAny(value, patterns) {
|
|
134
|
+
return patterns.some((pattern) => pattern.test(value));
|
|
135
|
+
}
|
|
136
|
+
function isFrontendNegated(clause) {
|
|
137
|
+
return matchesAny(clause, FRONTEND_NEGATION_PATTERNS);
|
|
138
|
+
}
|
|
139
|
+
function isDocumentationOnlyClause(clause) {
|
|
140
|
+
if (!DOCUMENTATION_ONLY_ACTIONS.test(clause))
|
|
141
|
+
return false;
|
|
142
|
+
if (DOCUMENTATION_ONLY_MARKERS.test(clause)) {
|
|
143
|
+
return !PRODUCT_AND_DOCUMENT_DELIVERY.test(clause);
|
|
144
|
+
}
|
|
145
|
+
return TEST_ONLY_MARKERS.test(clause) && !PRODUCT_AND_TEST_DELIVERY.test(clause);
|
|
146
|
+
}
|
|
147
|
+
function masksExistingBackendDependency(clause) {
|
|
148
|
+
return clause
|
|
149
|
+
.replace(/(?:调用|使用|消费|对接|依赖)(?:现有|已有)?[^。!?;,,\n]{0,12}(?:后端|服务端|API|接口)/gi, " ")
|
|
150
|
+
.replace(/\b(?:call|consume|use|integrate\s+with|depend\s+on)\b[^.?!;\n]{0,24}\b(?:existing|current)?\s*(?:backend|server[-\s]?side|api|endpoint)\b/gi, " ");
|
|
151
|
+
}
|
|
152
|
+
function addSignal(signals, signal) {
|
|
153
|
+
if (signals.some((current) => current.id === signal.id))
|
|
154
|
+
return;
|
|
155
|
+
signals.push(signal);
|
|
156
|
+
}
|
|
157
|
+
function collectTextSignals(input) {
|
|
158
|
+
let frontendDelivery = false;
|
|
159
|
+
let pathSupportedFrontendDelivery = false;
|
|
160
|
+
let backendDelivery = false;
|
|
161
|
+
let frontendNegated = false;
|
|
162
|
+
for (const clause of splitClauses(input.text)) {
|
|
163
|
+
const negated = isFrontendNegated(clause);
|
|
164
|
+
frontendNegated ||= negated;
|
|
165
|
+
const documentationOnly = isDocumentationOnlyClause(clause);
|
|
166
|
+
if (!negated &&
|
|
167
|
+
!documentationOnly &&
|
|
168
|
+
matchesAny(clause, FRONTEND_DELIVERY_PATTERNS)) {
|
|
169
|
+
frontendDelivery = true;
|
|
170
|
+
addSignal(input.signals, {
|
|
171
|
+
id: `frontend-delivery-${input.source}`,
|
|
172
|
+
source: input.source,
|
|
173
|
+
message: `${input.source} contains an implementation action and a frontend UI deliverable`,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
if (!negated &&
|
|
177
|
+
!documentationOnly &&
|
|
178
|
+
matchesAny(clause, PATH_SUPPORTED_FRONTEND_DELIVERY_PATTERNS)) {
|
|
179
|
+
pathSupportedFrontendDelivery = true;
|
|
180
|
+
}
|
|
181
|
+
const backendClause = masksExistingBackendDependency(clause);
|
|
182
|
+
if (!matchesAny(clause, BACKEND_NEGATION_PATTERNS) &&
|
|
183
|
+
matchesAny(backendClause, BACKEND_DELIVERY_PATTERNS)) {
|
|
184
|
+
backendDelivery = true;
|
|
185
|
+
addSignal(input.signals, {
|
|
186
|
+
id: `backend-delivery-${input.source}`,
|
|
187
|
+
source: input.source,
|
|
188
|
+
message: `${input.source} contains an implementation action and a backend deliverable`,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return {
|
|
193
|
+
frontendDelivery,
|
|
194
|
+
pathSupportedFrontendDelivery,
|
|
195
|
+
backendDelivery,
|
|
196
|
+
frontendNegated,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
function normalizedAllowedPath(value) {
|
|
200
|
+
return value.trim().replace(/\\/g, "/").replace(/^\.\//, "");
|
|
201
|
+
}
|
|
202
|
+
function reasonsForClassification(input) {
|
|
203
|
+
const signalIds = input.signals.map((signal) => signal.id);
|
|
204
|
+
const reasons = [`task demand classified as ${input.kind}`];
|
|
205
|
+
if (signalIds.length > 0)
|
|
206
|
+
reasons.push(`signals: ${signalIds.join(", ")}`);
|
|
207
|
+
if (input.blockers.length > 0)
|
|
208
|
+
reasons.push(`blockers: ${input.blockers.join(", ")}`);
|
|
209
|
+
return reasons;
|
|
210
|
+
}
|
|
211
|
+
export function classifyTaskDemand(input) {
|
|
212
|
+
const signals = [];
|
|
213
|
+
const blockers = [];
|
|
214
|
+
const requirement = normalizeTaskRequirementText(input.requirementMarkdown);
|
|
215
|
+
const hasFrontendTaskType = FRONTEND_TASK_TYPE.test(requirement);
|
|
216
|
+
const hasBackendTaskType = BACKEND_TASK_TYPE.test(requirement);
|
|
217
|
+
if (hasFrontendTaskType) {
|
|
218
|
+
addSignal(signals, {
|
|
219
|
+
id: "task-type-frontend-feature",
|
|
220
|
+
source: "requirement",
|
|
221
|
+
message: "requirement contains the materialized frontend-feature task type",
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
if (hasBackendTaskType) {
|
|
225
|
+
addSignal(signals, {
|
|
226
|
+
id: "task-type-backend-feature",
|
|
227
|
+
source: "requirement",
|
|
228
|
+
message: "requirement contains the materialized backend-feature task type",
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
const titleSignals = collectTextSignals({
|
|
232
|
+
text: input.title,
|
|
233
|
+
source: "title",
|
|
234
|
+
signals,
|
|
235
|
+
});
|
|
236
|
+
const requirementSignals = collectTextSignals({
|
|
237
|
+
text: requirement,
|
|
238
|
+
source: "requirement",
|
|
239
|
+
signals,
|
|
240
|
+
});
|
|
241
|
+
const frontendNegated = titleSignals.frontendNegated || requirementSignals.frontendNegated;
|
|
242
|
+
if (frontendNegated)
|
|
243
|
+
blockers.push("frontend-negated");
|
|
244
|
+
let frontendPath = false;
|
|
245
|
+
let backendPath = false;
|
|
246
|
+
for (const rawPath of input.allowedPaths) {
|
|
247
|
+
const allowedPath = normalizedAllowedPath(rawPath);
|
|
248
|
+
if (!FRONTEND_PATH_EXCLUSIONS.test(allowedPath) &&
|
|
249
|
+
matchesAny(allowedPath, FRONTEND_PATH_PATTERNS)) {
|
|
250
|
+
frontendPath = true;
|
|
251
|
+
}
|
|
252
|
+
if (matchesAny(allowedPath, BACKEND_PATH_PATTERNS))
|
|
253
|
+
backendPath = true;
|
|
254
|
+
}
|
|
255
|
+
if (frontendPath) {
|
|
256
|
+
addSignal(signals, {
|
|
257
|
+
id: "frontend-path",
|
|
258
|
+
source: "allowed-path",
|
|
259
|
+
message: "allowedPaths include a frontend-specific source path",
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
if (backendPath) {
|
|
263
|
+
addSignal(signals, {
|
|
264
|
+
id: "backend-path",
|
|
265
|
+
source: "allowed-path",
|
|
266
|
+
message: "allowedPaths include a backend-specific source path",
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
const pathSupportedFrontendDelivery = frontendPath &&
|
|
270
|
+
(titleSignals.pathSupportedFrontendDelivery ||
|
|
271
|
+
requirementSignals.pathSupportedFrontendDelivery);
|
|
272
|
+
if (pathSupportedFrontendDelivery) {
|
|
273
|
+
addSignal(signals, {
|
|
274
|
+
id: requirementSignals.pathSupportedFrontendDelivery
|
|
275
|
+
? "frontend-path-supported-delivery-requirement"
|
|
276
|
+
: "frontend-path-supported-delivery-title",
|
|
277
|
+
source: requirementSignals.pathSupportedFrontendDelivery
|
|
278
|
+
? "requirement"
|
|
279
|
+
: "title",
|
|
280
|
+
message: "task text contains a frontend UI artifact and allowedPaths provide frontend source-path evidence",
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
const frontendDelivery = titleSignals.frontendDelivery ||
|
|
284
|
+
requirementSignals.frontendDelivery ||
|
|
285
|
+
pathSupportedFrontendDelivery;
|
|
286
|
+
const allowedPathsOnlyCoverNonProductArtifacts = input.allowedPaths.length > 0 &&
|
|
287
|
+
input.allowedPaths.every((rawPath) => {
|
|
288
|
+
const allowedPath = normalizedAllowedPath(rawPath);
|
|
289
|
+
return matchesAny(allowedPath, NON_PRODUCT_ALLOWED_PATH_PATTERNS);
|
|
290
|
+
});
|
|
291
|
+
if (allowedPathsOnlyCoverNonProductArtifacts && frontendDelivery) {
|
|
292
|
+
blockers.push("non-product-allowed-paths");
|
|
293
|
+
}
|
|
294
|
+
const eligibleFrontendDelivery = frontendDelivery && !allowedPathsOnlyCoverNonProductArtifacts;
|
|
295
|
+
const backendDelivery = titleSignals.backendDelivery || requirementSignals.backendDelivery;
|
|
296
|
+
const taskTypeConflict = (hasFrontendTaskType && (backendDelivery || frontendNegated)) ||
|
|
297
|
+
(hasBackendTaskType && eligibleFrontendDelivery);
|
|
298
|
+
if (taskTypeConflict)
|
|
299
|
+
blockers.push("task-type-conflict");
|
|
300
|
+
let kind;
|
|
301
|
+
if ((hasFrontendTaskType && hasBackendTaskType) || taskTypeConflict) {
|
|
302
|
+
kind = "mixed";
|
|
303
|
+
}
|
|
304
|
+
else if (hasFrontendTaskType) {
|
|
305
|
+
kind = "frontend";
|
|
306
|
+
}
|
|
307
|
+
else if (hasBackendTaskType) {
|
|
308
|
+
kind = "backend";
|
|
309
|
+
}
|
|
310
|
+
else if (eligibleFrontendDelivery && backendDelivery) {
|
|
311
|
+
kind = "mixed";
|
|
312
|
+
}
|
|
313
|
+
else if (eligibleFrontendDelivery && frontendNegated) {
|
|
314
|
+
kind = "mixed";
|
|
315
|
+
}
|
|
316
|
+
else if (eligibleFrontendDelivery) {
|
|
317
|
+
kind = "frontend";
|
|
318
|
+
}
|
|
319
|
+
else if (backendDelivery) {
|
|
320
|
+
kind = "backend";
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
kind = "unknown";
|
|
324
|
+
}
|
|
325
|
+
return {
|
|
326
|
+
kind,
|
|
327
|
+
signals,
|
|
328
|
+
blockers,
|
|
329
|
+
reasons: reasonsForClassification({ kind, signals, blockers }),
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
export function resolveTaskDagTemplateSelection(input) {
|
|
333
|
+
const requestedTemplate = input.requestedTemplate ?? "standard-dag";
|
|
334
|
+
const taskKindTemplate = SPECIAL_TEMPLATE_BY_TASK_KIND[input.taskKind];
|
|
335
|
+
if (taskKindTemplate) {
|
|
336
|
+
return {
|
|
337
|
+
template: taskKindTemplate,
|
|
338
|
+
source: "taskKind",
|
|
339
|
+
reasons: [TASK_KIND_ROUTING_REASON[input.taskKind]],
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
if (SPECIAL_REQUESTED_TEMPLATES.has(requestedTemplate)) {
|
|
343
|
+
return {
|
|
344
|
+
template: requestedTemplate,
|
|
345
|
+
source: "requested-template",
|
|
346
|
+
reasons: [],
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
if (input.taskKind !== "standard") {
|
|
350
|
+
return {
|
|
351
|
+
template: requestedTemplate,
|
|
352
|
+
source: "requested-template",
|
|
353
|
+
reasons: [],
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
if (requestedTemplate !== "standard-dag") {
|
|
357
|
+
return {
|
|
358
|
+
template: requestedTemplate,
|
|
359
|
+
source: "requested-template",
|
|
360
|
+
reasons: [],
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
const classification = classifyTaskDemand(input);
|
|
364
|
+
if (classification.kind === "frontend") {
|
|
365
|
+
const frontendSignals = classification.signals
|
|
366
|
+
.map((signal) => signal.id)
|
|
367
|
+
.filter((id) => id === "task-type-frontend-feature" || id.startsWith("frontend-"));
|
|
368
|
+
return {
|
|
369
|
+
template: "frontend-implementation",
|
|
370
|
+
source: "task-source",
|
|
371
|
+
reasons: [
|
|
372
|
+
`task source classified as frontend from signals: ${frontendSignals.join(", ")}`,
|
|
373
|
+
],
|
|
374
|
+
classification,
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
return {
|
|
378
|
+
template: requestedTemplate,
|
|
379
|
+
source: "requested-template",
|
|
380
|
+
reasons: [],
|
|
381
|
+
classification,
|
|
382
|
+
};
|
|
383
|
+
}
|