@tea-agent/loop-agent 0.16.1-beta.2 → 0.16.2
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 +4 -8
- package/CHANGELOG.md +76 -18
- package/README.md +76 -299
- package/dist/application/evaluation/alias.js +184 -0
- package/dist/application/evaluation/budget.js +192 -0
- package/dist/application/evaluation/campaign-hash.js +47 -0
- package/dist/application/evaluation/campaign-matrix.js +372 -0
- package/dist/application/evaluation/campaign-scorecard.js +135 -0
- package/dist/application/evaluation/campaign.js +370 -0
- package/dist/application/evaluation/candidate.js +23 -6
- package/dist/application/evaluation/corpus-hash.js +38 -0
- package/dist/application/evaluation/corpus.js +56 -0
- package/dist/application/evaluation/experiment.js +294 -0
- package/dist/application/evaluation/ignition.js +198 -0
- package/dist/application/evaluation/integrity-audit.js +162 -0
- package/dist/application/evaluation/outer-loop.js +132 -0
- package/dist/application/evaluation/pi-cell-executor.js +39 -0
- package/dist/application/evaluation/private-verifier.js +46 -0
- package/dist/application/evaluation/promotion-policy.js +151 -0
- package/dist/application/evaluation/proposer.js +98 -0
- package/dist/application/evaluation/types.js +522 -0
- package/dist/cli/command-definitions.js +19 -3
- package/dist/commands/dag-reconcile-run.js +3 -116
- package/dist/commands/eval.js +1176 -13
- package/dist/commands/init.js +7 -1
- package/dist/executors/dag-pi-executor.js +8 -46
- package/dist/executors/pi-sdk-executor.js +66 -3
- package/dist/executors/shell-executor.js +213 -30
- package/dist/executors/shell-presets.js +12 -2
- package/dist/executors/shell-write-guard.js +20 -1
- package/dist/infrastructure/evaluation/alias-store.js +199 -0
- package/dist/infrastructure/evaluation/campaign-store.js +154 -0
- package/dist/infrastructure/evaluation/corpus-store.js +181 -0
- package/dist/infrastructure/evaluation/experiment-store.js +124 -0
- package/dist/infrastructure/evaluation/ignition-store.js +82 -0
- package/dist/infrastructure/evaluation/private-verifier-store.js +145 -0
- package/dist/infrastructure/evaluation/proposer-store.js +78 -0
- package/dist/records/promotion.js +3 -1
- package/dist/shared/git-progress.js +9 -2
- package/dist/worker/cli.js +83 -0
- package/dist/worker/delivery/git-transaction.js +75 -0
- package/dist/worker/delivery/verification-bundle.js +13 -2
- package/dist/worker/feature/review.js +3 -2
- package/dist/worker/observability/read-model.js +56 -0
- package/dist/worker/observe/server.js +6 -3
- package/dist/worker/observe/static/dag-helpers.js +0 -62
- package/dist/worker/observe/static/styles.css +18 -55
- package/dist/worker/observe/static/views/dag.js +13 -5
- package/dist/worker/outcomes/adapters.js +4 -1
- package/dist/worker/outcomes/declared-artifacts.js +103 -0
- package/dist/worker/outcomes/evidence-tokens.js +29 -0
- package/dist/worker/outcomes/gate.js +10 -11
- package/dist/worker/outcomes/projector.js +30 -4
- package/dist/worker/outcomes/types.js +3 -0
- package/dist/worker/pool/reconcile.js +285 -0
- package/dist/worker/run-task/run-task.js +81 -4
- package/dist/worker/runner/run-ready.js +25 -2
- package/dist/worker/task-graph/ready-planner.js +14 -8
- package/dist/worker/task-graph/task-graph-schema.js +5 -3
- package/dist/workflows/dag/backend-test-analysis-contract.js +87 -30
- package/dist/workflows/dag/backend-test-case-manifest.js +71 -8
- package/dist/workflows/dag/backend-test-execution-contract.js +63 -11
- package/dist/workflows/dag/backend-test-repair-contract.js +94 -0
- package/dist/workflows/dag/backend-test-result-contract.js +6 -4
- package/dist/workflows/dag/backend-test-semantic-review-contract.js +36 -0
- package/dist/workflows/dag/budget-enforcement.js +67 -0
- package/dist/workflows/dag/context-policy.js +137 -0
- package/dist/workflows/dag/dynamic-runtime/condition.js +1 -1
- package/dist/workflows/dag/dynamic-runtime/shared.js +42 -0
- package/dist/workflows/dag/failure-routing.js +8 -1
- package/dist/workflows/dag/frontend-implementation-contract.js +32 -93
- package/dist/workflows/dag/init-hybrid.js +624 -172
- package/dist/workflows/dag/knowledge-curator.js +3 -0
- package/dist/workflows/dag/lifecycle.js +33 -2
- package/dist/workflows/dag/node-execution.js +11 -4
- package/dist/workflows/dag/prompt.js +1 -1
- package/dist/workflows/dag/reconcile-run.js +121 -0
- package/dist/workflows/dag/report.js +12 -0
- package/dist/workflows/dag/runner.js +43 -16
- package/dist/workflows/dag/scheduler.js +87 -17
- package/dist/workflows/dag/skill-snapshot.js +11 -7
- package/dist/workflows/dag/types.js +49 -1
- package/dist/workflows/dag/validate.js +35 -15
- package/docs/README.md +3 -1
- package/docs/architecture/runtime-boundaries.md +3 -2
- package/docs/init-surface.manifest.json +4 -0
- package/docs/local-development-environment.md +52 -0
- package/docs/templates/agent-dag.schema.json +25 -7
- package/docs/templates/agent-dag.supervised-implementation.json +23 -4
- package/docs/templates/backend-test-analysis.schema.json +9 -16
- package/docs/templates/backend-test-dag.json +493 -197
- package/docs/templates/backend-test-dag.review-cases.prompt.md +10 -4
- package/docs/templates/backend-test-execution.schema.json +6 -1
- package/docs/templates/branch-merge-report.md +14 -0
- package/docs/templates/evaluation/campaign-budget-v1.json +12 -0
- package/docs/templates/evaluation/campaign-dogfood-v0.json +24 -0
- package/docs/templates/evaluation/campaign-evidence-v1.json +44 -0
- package/docs/templates/evaluation/context-policy-baseline-v1.json +17 -0
- package/docs/templates/evaluation/context-policy-role-specialized-v1.json +28 -0
- package/docs/templates/evaluation/corpus-dogfood-v0.manifest.json +118 -0
- package/docs/templates/evaluation/matrix-dag-dry-run-v1.json +21 -0
- package/docs/templates/evaluation/matrix-fixture-v1.json +10 -0
- package/docs/templates/evaluation/private-verifier-dogfood-v0.json +16 -0
- package/docs/templates/product-line/AGENTS.md +1 -0
- package/docs/templates/product-line/README.md +17 -0
- package/docs/templates/product-line/acceptance.yaml +9 -0
- package/docs/templates/product-line/feature.yaml +11 -0
- package/docs/templates/product-line/task-graph.yaml +8 -0
- package/docs/templates/product-line/task.yaml +4 -0
- package/package.json +2 -1
- package/skills/frontend-implementation/references/node-contracts.md +3 -3
- package/skills/loop-agent/references/command-reference.md +5 -0
- package/skills/loop-agent/references/hybrid-dag.md +7 -4
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { campaignBudgetSchema, } from "../../application/evaluation/budget.js";
|
|
2
3
|
import { assertDagPromptSourceRule } from "./prompt-source.js";
|
|
3
4
|
import { dagRetryPolicySchema } from "./retry-policy.js";
|
|
4
5
|
export const dagComplexitySchema = z.enum(["HIGH", "MED", "LOW"]);
|
|
@@ -58,6 +59,14 @@ export const dagVerdictGateSchema = z.object({
|
|
|
58
59
|
fromNodeId: z
|
|
59
60
|
.string()
|
|
60
61
|
.regex(/^[a-z][a-z0-9-]*$/, "fromNodeId must be kebab-case"),
|
|
62
|
+
/**
|
|
63
|
+
* Optional fallback node ids when the primary `fromNodeId` JSON is absent
|
|
64
|
+
* (e.g. exclusive condition branch skip). Tried in order after primary.
|
|
65
|
+
* Prefer listing the post-revision / final reviewer first when both may exist.
|
|
66
|
+
*/
|
|
67
|
+
fallbackFromNodeIds: z
|
|
68
|
+
.array(z.string().regex(/^[a-z][a-z0-9-]*$/, "fallbackFromNodeIds must be kebab-case"))
|
|
69
|
+
.optional(),
|
|
61
70
|
accept: z.array(z.string().min(1)).min(1),
|
|
62
71
|
lineMode: z.enum(["first-non-empty", "first-verdict-line"]).optional(),
|
|
63
72
|
label: z.string().min(1).optional(),
|
|
@@ -75,8 +84,11 @@ export const dagRequirementCoverageGateSchema = z.object({
|
|
|
75
84
|
});
|
|
76
85
|
export const dagJsonArtifactSchemaIdSchema = z.enum([
|
|
77
86
|
"backend-test-analysis-v1",
|
|
87
|
+
"backend-test-analysis-v2",
|
|
78
88
|
"backend-test-execution-v1",
|
|
79
89
|
"backend-test-result-v1",
|
|
90
|
+
"backend-test-classification-v1",
|
|
91
|
+
"backend-test-semantic-review-v1",
|
|
80
92
|
"backend-test-case-manifest-v1",
|
|
81
93
|
"frontend-implementation-contract-v1",
|
|
82
94
|
]);
|
|
@@ -85,6 +97,7 @@ export const dagJsonArtifactGateSchema = z.object({
|
|
|
85
97
|
schemaId: dagJsonArtifactSchemaIdSchema,
|
|
86
98
|
artifactName: z.string().regex(/^[a-z0-9][a-z0-9._-]*\.json$/),
|
|
87
99
|
outputDir: z.string().regex(/^[a-z0-9][a-z0-9._-]*$/),
|
|
100
|
+
junitRelativePath: z.string().min(1).optional(),
|
|
88
101
|
});
|
|
89
102
|
export const ENV_VAR_NAME_PATTERN = /^[A-Z_][A-Z0-9_]*$/;
|
|
90
103
|
export const dagVersionSchema = z
|
|
@@ -105,12 +118,18 @@ export const dagWritePolicySchema = z.enum([
|
|
|
105
118
|
"exclusive",
|
|
106
119
|
"none",
|
|
107
120
|
]);
|
|
121
|
+
export const contextPolicyIdSchema = z.enum([
|
|
122
|
+
"baseline-v1",
|
|
123
|
+
"role-specialized-v1",
|
|
124
|
+
]);
|
|
108
125
|
export const dagDefaultsSchema = z
|
|
109
126
|
.object({
|
|
110
127
|
executor: dagNodeExecutorSchema.optional(),
|
|
111
128
|
model: z.string().optional(),
|
|
112
129
|
piBackend: z.enum(["sdk-first", "cli-only"]).optional(),
|
|
113
130
|
contextProfile: z.string().optional(),
|
|
131
|
+
/** Eval Lab ContextPolicy seam; default baseline-v1 when omitted. */
|
|
132
|
+
contextPolicyId: contextPolicyIdSchema.optional(),
|
|
114
133
|
skills: z.array(z.string()).optional(),
|
|
115
134
|
writePolicy: dagWritePolicySchema.optional(),
|
|
116
135
|
})
|
|
@@ -122,12 +141,22 @@ export const dagNodeStatusSchema = z.enum([
|
|
|
122
141
|
"ERROR",
|
|
123
142
|
"SKIPPED",
|
|
124
143
|
]);
|
|
144
|
+
export const dagBackendTestPipelineSchema = z.enum([
|
|
145
|
+
"contracts",
|
|
146
|
+
"semantic-initial",
|
|
147
|
+
"semantic-final",
|
|
148
|
+
"execute-parse-initial",
|
|
149
|
+
"classification-eligibility",
|
|
150
|
+
"repair-safety-traceability",
|
|
151
|
+
"finalize-effective-result",
|
|
152
|
+
]);
|
|
125
153
|
export const dagShellConfigSchema = z.object({
|
|
126
154
|
commands: z.array(z.string()).default([]),
|
|
127
155
|
preset: dagShellPresetSchema.optional(),
|
|
128
156
|
verdictGate: dagVerdictGateSchema.optional(),
|
|
129
157
|
requirementCoverageGate: dagRequirementCoverageGateSchema.optional(),
|
|
130
158
|
jsonArtifactGate: dagJsonArtifactGateSchema.optional(),
|
|
159
|
+
backendTestPipeline: dagBackendTestPipelineSchema.optional(),
|
|
131
160
|
verifyEvidence: dagShellVerifyEvidenceSchema.optional(),
|
|
132
161
|
repairArtifactGate: dagRepairArtifactGateSchema.optional(),
|
|
133
162
|
/** fail (default): any nonzero command fails the node. record: finish node FINISHED with failure facts for downstream assess/repair. */
|
|
@@ -248,6 +277,15 @@ export const dagConvergenceSpecSchema = z
|
|
|
248
277
|
export const dagTaskSchema = z.object({
|
|
249
278
|
id: z.string().regex(/^[a-z][a-z0-9-]*$/, "task id must be kebab-case"),
|
|
250
279
|
depends_on: z.array(z.string()).default([]),
|
|
280
|
+
/**
|
|
281
|
+
* How SKIPPED upstreams affect readiness.
|
|
282
|
+
* - all (default): any SKIPPED/ERROR dependency blocks (cascade exclusive branches)
|
|
283
|
+
* - all-or-condition-skip: condition-branch SKIPPED tips count as soft; node runs when
|
|
284
|
+
* every dep is FINISHED or soft condition-skip and at least one is FINISHED (OR-join)
|
|
285
|
+
*/
|
|
286
|
+
dependsPolicy: z.enum(["all", "all-or-condition-skip"]).optional(),
|
|
287
|
+
/** Execute only when this fail-closed condition resolves true after dependencies settle. */
|
|
288
|
+
runIf: z.string().min(1).optional(),
|
|
251
289
|
complexity: dagComplexitySchema,
|
|
252
290
|
subtask_prompt: z.string().min(1),
|
|
253
291
|
subtask_prompt_source: z
|
|
@@ -267,7 +305,6 @@ export const dagTaskSchema = z.object({
|
|
|
267
305
|
shell: dagShellConfigSchema.optional(),
|
|
268
306
|
static: dagStaticConfigSchema.optional(),
|
|
269
307
|
outputContract: z.string().optional(),
|
|
270
|
-
firstProtocolLine: z.string().min(1).optional(),
|
|
271
308
|
allowedPaths: z.array(z.string()).optional().default([]),
|
|
272
309
|
forbiddenPaths: z.array(z.string()).optional().default([]),
|
|
273
310
|
decisionGate: dagDecisionGateSchema.optional(),
|
|
@@ -301,6 +338,8 @@ export const dagEvaluationBindingSchema = z
|
|
|
301
338
|
taskRef: z.string().min(1).optional(),
|
|
302
339
|
})
|
|
303
340
|
.strict();
|
|
341
|
+
/** Eval Lab Campaign Budget (W3.4); enforced by runner ledger when mode=hard. */
|
|
342
|
+
export const dagBudgetSchema = campaignBudgetSchema;
|
|
304
343
|
export const dagSourceBindingSchema = z.object({
|
|
305
344
|
schemaVersion: z.literal(1),
|
|
306
345
|
taskId: z.string().min(1),
|
|
@@ -321,6 +360,8 @@ export const dagSpecSchema = z
|
|
|
321
360
|
title: z.string().min(1),
|
|
322
361
|
runtimeContract: dagRuntimeContractSchema.optional(),
|
|
323
362
|
evaluation: dagEvaluationBindingSchema.optional(),
|
|
363
|
+
/** Optional hard/record-only budget; requires version 3. */
|
|
364
|
+
budget: dagBudgetSchema.optional(),
|
|
324
365
|
sourceBinding: dagSourceBindingSchema.optional(),
|
|
325
366
|
outputLanguage: dagOutputLanguageSchema.optional(),
|
|
326
367
|
objective: z.string().optional(),
|
|
@@ -341,6 +382,13 @@ export const dagSpecSchema = z
|
|
|
341
382
|
path: ["evaluation"],
|
|
342
383
|
});
|
|
343
384
|
}
|
|
385
|
+
if (spec.budget && spec.version !== 3) {
|
|
386
|
+
ctx.addIssue({
|
|
387
|
+
code: z.ZodIssueCode.custom,
|
|
388
|
+
message: "budget requires DagSpec version 3",
|
|
389
|
+
path: ["budget"],
|
|
390
|
+
});
|
|
391
|
+
}
|
|
344
392
|
if (spec.runtimeContract && spec.version !== 3) {
|
|
345
393
|
ctx.addIssue({
|
|
346
394
|
code: z.ZodIssueCode.custom,
|
|
@@ -137,18 +137,24 @@ function validateVerdictGateConfig(task, spec, issues) {
|
|
|
137
137
|
return;
|
|
138
138
|
}
|
|
139
139
|
const upstreamIds = new Set(task.depends_on);
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
140
|
+
const sourceNodeIds = [
|
|
141
|
+
verdictGate.fromNodeId,
|
|
142
|
+
...(verdictGate.fallbackFromNodeIds ?? []),
|
|
143
|
+
];
|
|
144
|
+
for (const sourceId of sourceNodeIds) {
|
|
145
|
+
if (!upstreamIds.has(sourceId)) {
|
|
146
|
+
issues.push({
|
|
147
|
+
type: "invalid-verdict-gate-config",
|
|
148
|
+
message: `task ${task.id} shell.verdictGate source "${sourceId}" must appear in depends_on`,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
const upstreamTask = spec.tasks.find((candidate) => candidate.id === sourceId);
|
|
152
|
+
if (upstreamTask && upstreamTask.executor === "shell") {
|
|
153
|
+
issues.push({
|
|
154
|
+
type: "invalid-verdict-gate-config",
|
|
155
|
+
message: `task ${task.id} shell.verdictGate source "${sourceId}" must reference a non-shell upstream verdict node`,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
152
158
|
}
|
|
153
159
|
for (const accepted of verdictGate.accept) {
|
|
154
160
|
if (accepted.trim().length === 0) {
|
|
@@ -229,7 +235,10 @@ function validateRepairArtifactGateConfig(task, spec, issues) {
|
|
|
229
235
|
candidate.role === "reviewer" &&
|
|
230
236
|
!candidate.decisionGate?.enabled &&
|
|
231
237
|
candidate.writePolicy === "read-only" &&
|
|
232
|
-
(candidate.outputContract?.includes("VERDICT:") ?? false)
|
|
238
|
+
(candidate.outputContract?.includes("VERDICT:") ?? false) &&
|
|
239
|
+
// Format recovery / write-set format-repair nodes also emit VERDICT but
|
|
240
|
+
// are not the primary content review after hard verification.
|
|
241
|
+
!/(?:-format-repair-pi|-verdict-recovery-pi)$/.test(candidate.id));
|
|
233
242
|
if (reviewCandidates.length !== 1) {
|
|
234
243
|
issues.push({
|
|
235
244
|
type: "invalid-repair-artifact-gate-config",
|
|
@@ -343,6 +352,17 @@ function validateTaskWritePolicy(task, spec, issues) {
|
|
|
343
352
|
return;
|
|
344
353
|
}
|
|
345
354
|
for (const entry of writeSet) {
|
|
355
|
+
const normalizedEntry = entry.trim().replace(/\\/g, "/").replace(/^\.\//, "");
|
|
356
|
+
if (writePolicy === "exclusive" &&
|
|
357
|
+
(normalizedEntry === "**" ||
|
|
358
|
+
normalizedEntry === "./**" ||
|
|
359
|
+
normalizedEntry === "*")) {
|
|
360
|
+
issues.push({
|
|
361
|
+
type: "write-set-unbounded",
|
|
362
|
+
message: `task ${task.id} writeSet entry "${entry}" is unbounded; exclusive writers must use concrete paths or limited globs`,
|
|
363
|
+
});
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
346
366
|
if (forbiddenPaths.length > 0 &&
|
|
347
367
|
writeSetEntryHitsForbidden(entry, forbiddenPaths)) {
|
|
348
368
|
issues.push({
|
|
@@ -426,10 +446,10 @@ function validateShellTaskConfig(task, spec, issues) {
|
|
|
426
446
|
return;
|
|
427
447
|
}
|
|
428
448
|
const commands = resolveShellCommands(shell);
|
|
429
|
-
if (commands.length === 0 && !shell.jsonArtifactGate) {
|
|
449
|
+
if (commands.length === 0 && !shell.jsonArtifactGate && !shell.backendTestPipeline) {
|
|
430
450
|
issues.push({
|
|
431
451
|
type: "missing-shell-commands",
|
|
432
|
-
message: `shell task ${task.id} requires shell.preset, shell.verdictGate, and/or non-empty shell.commands`,
|
|
452
|
+
message: `shell task ${task.id} requires shell.preset, shell.verdictGate, shell.jsonArtifactGate, shell.backendTestPipeline, and/or non-empty shell.commands`,
|
|
433
453
|
});
|
|
434
454
|
}
|
|
435
455
|
if (commands.some((command) => command.trim().length === 0)) {
|
package/docs/README.md
CHANGED
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
- `design/archive/2026-07-14-loop-agent-self-update-notifier.md` — loop-agent CLI 自更新提醒设计(已实现;历史设计说明)
|
|
14
14
|
- `development-principles.md` — 仓库开发原则
|
|
15
15
|
- `github-collaboration.md` — 内部研发人员的轻量 GitHub 协作指南:短分支、简短 PR、CI 与 Squash Merge
|
|
16
|
+
- `local-development-environment.md` — Cursor Cloud 等特定本地开发环境的已知问题与排障方法
|
|
17
|
+
- `branch-merge-guideline.md` — 分支合并标准流程:快速/标准/深度模式、功能保留、冲突解析、init/package 审计与 source-SHA 报告
|
|
16
18
|
- `architecture/runtime-boundaries.md` — runtime 层边界与依赖方向
|
|
17
19
|
- `architecture/README.md` — 架构文档目录索引与阅读路径
|
|
18
20
|
- `architecture/system-overview.md` — loop-agent / agent-worker / 治理层 / 外部系统全景
|
|
@@ -84,7 +86,7 @@
|
|
|
84
86
|
- `templates/product-line/` — 可投影的 Feature/Task/QA/Links 产品线包;配合 `agent-worker task validate-feature` 做 docs CI
|
|
85
87
|
- `templates/production-readiness-checklist.md` — 低/中风险单仓库 DAG readiness 检查清单
|
|
86
88
|
- `templates/init-evolution-review.md` — 初始化能力演化审查报告模板
|
|
87
|
-
- `templates/branch-merge-report.md` —
|
|
89
|
+
- `templates/branch-merge-report.md` — 跨分支合并的模式选择、功能保留、冲突解析、source drift、init/update 与 package surface 审计模板
|
|
88
90
|
- `templates/adr.md` — 架构决策记录(ADR)
|
|
89
91
|
|
|
90
92
|
## 维护
|
|
@@ -85,14 +85,15 @@ Governance (scripts/check-*.sh, src/governance/)
|
|
|
85
85
|
- **职责**:TaskSpec 校验与物化、Task Pool batch/retry/morning report under `.harness/task-pool/`、失败路由,以及只读 Observe 事件/快照/UI;实际 DAG 执行通过 `LoopAgentClient` 启动已发布的 `loop-agent` 子进程。自 0.8.0 起该目录是唯一受支持的 Task Pool runtime root,旧路径不读取、不迁移、不合并、不重映射。
|
|
86
86
|
- **controller identity**:写入型 Feature/batch/Task/final verification 在任何目标仓库或 Task Pool 状态写入前解析并冻结 schemaVersion 1 identity,包括 package name/version、绝对 entry/real entry、直接可执行 launch spec、binary SHA-256 和覆盖 `package.json`、`bin/**`、`dist/**`、`.agents/skills/**` 的 portable fingerprint。expected version/fingerprint 不匹配时 fail-fast;后续 spawn 不重新查询 PATH。
|
|
87
87
|
- **证据传播**:canonical Worker record、Task Pool run、batch/Feature、QA/final evidence 可选携带同一 identity;只有所有相关层都省略 identity 时才按 legacy evidence 接受,部分缺失或锚点不一致会拒绝。
|
|
88
|
-
-
|
|
88
|
+
- **Worker-aware recovery**:`agent-worker task reconcile --action abandon` 由 Worker 持有 Worker run ↔ DAG run association,再调用 DAG-kernel reconcile(`src/workflows/dag/reconcile-run.ts`)并把 Task Pool 投影为 `Failed` + recovery evidence。低层 `loop-agent dag reconcile-run` 只维护 DAG lifecycle,不搜索/修改 Task Pool。应急入口 `pool mark-failed` 同属 Worker,禁止覆盖 `Done`。
|
|
89
|
+
- **允许依赖**:TaskSpec、Task Pool、observability、Node filesystem/path、明确的 shared/task contract,以及只读/共享的 `src/workflows/dag/**` reconcile/lifecycle helpers;它不是第二套 executor 或 DAG kernel。
|
|
89
90
|
- **禁止**:in-process import `src/cli/**`、`src/commands/**` 或 `src/application/**`。
|
|
90
91
|
|
|
91
92
|
### Infrastructure / Store
|
|
92
93
|
|
|
93
94
|
- **位置**:`src/infrastructure/harness/**`(按计划逐步引入);过渡期部分逻辑仍在 `src/workflows/dag/lifecycle.ts`、`src/records/**`。
|
|
94
95
|
- **职责**:`.harness/tasks`、`.harness/dag-runs`、`.harness/runs`、loop state 的集中读写;completed run facts 只读约束。DAG run 自有的 `.runtime/skill-snapshot.json` 随 lifecycle 目录整体迁移,state 只保存相对 ref 和 hash,不保存 active/paused/completed 绝对路径。
|
|
95
|
-
- **DAG recovery mutation**:`dag reconcile-run` 是显式 operator 边界;默认仅检查,只有给出 action + reason 且 runner 已证明停止时才能保存原始快照、写 terminal reconciliation facts 并迁移 lifecycle。Observe、status 和 doctor 始终只读。
|
|
96
|
+
- **DAG recovery mutation**:`dag reconcile-run` 是显式 operator 边界;默认仅检查,只有给出 action + reason 且 runner 已证明停止时才能保存原始快照、写 terminal reconciliation facts 并迁移 lifecycle。Observe、status 和 doctor 始终只读。Task Pool 侧的 abandon/Failed 投影由 `agent-worker task reconcile` 持有,不得让 DAG kernel 反向依赖 Worker。
|
|
96
97
|
- **禁止**:把 raw path mutation 扩散给 runner、loop action 或 command handler。
|
|
97
98
|
|
|
98
99
|
### Governance
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"docs/harness-methodology-tdd.md",
|
|
12
12
|
"docs/harness-methodology-verification.md",
|
|
13
13
|
"docs/harness-methodology-debugging.md",
|
|
14
|
+
"docs/local-development-environment.md",
|
|
14
15
|
"docs/init-surface.manifest.json",
|
|
15
16
|
"docs/architecture/runtime-boundaries.md",
|
|
16
17
|
"docs/architecture/README.md",
|
|
@@ -39,6 +40,7 @@
|
|
|
39
40
|
"docs/templates/product-line/test-plan.md",
|
|
40
41
|
"docs/templates/product-line/task-graph.yaml",
|
|
41
42
|
"docs/templates/product-line/task.yaml",
|
|
43
|
+
"docs/templates/product-line/feature.yaml",
|
|
42
44
|
"docs/templates/product-line/closeout.yaml",
|
|
43
45
|
"docs/templates/product-line/links.md",
|
|
44
46
|
"scripts/kb-bootstrap-init-skeleton.sh",
|
|
@@ -106,6 +108,7 @@
|
|
|
106
108
|
"docs/templates/product-line/test-plan.md",
|
|
107
109
|
"docs/templates/product-line/task-graph.yaml",
|
|
108
110
|
"docs/templates/product-line/task.yaml",
|
|
111
|
+
"docs/templates/product-line/feature.yaml",
|
|
109
112
|
"docs/templates/product-line/closeout.yaml",
|
|
110
113
|
"docs/templates/product-line/links.md",
|
|
111
114
|
"scripts/check-skill-entry.sh",
|
|
@@ -175,6 +178,7 @@
|
|
|
175
178
|
"docs/templates/product-line/test-plan.md": "copied",
|
|
176
179
|
"docs/templates/product-line/task-graph.yaml": "copied",
|
|
177
180
|
"docs/templates/product-line/task.yaml": "copied",
|
|
181
|
+
"docs/templates/product-line/feature.yaml": "copied",
|
|
178
182
|
"docs/templates/product-line/closeout.yaml": "copied",
|
|
179
183
|
"docs/templates/product-line/links.md": "copied",
|
|
180
184
|
"scripts/check-skill-entry.sh": "generated",
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# 本地开发环境排障
|
|
2
|
+
|
|
3
|
+
本文记录仅与特定开发环境有关、但可能反复影响验证结果的问题。它不是通用工程规则;标准验证命令仍以 [`verification-matrix.md`](verification-matrix.md) 为准。
|
|
4
|
+
|
|
5
|
+
## Cursor Cloud
|
|
6
|
+
|
|
7
|
+
Cursor Cloud VM 当前有两个需要特别注意的环境问题。
|
|
8
|
+
|
|
9
|
+
### Node.js 版本
|
|
10
|
+
|
|
11
|
+
VM 默认 `node`(`/exec-daemon/node`)可能是 v22.14.0,但可选依赖 `@earendil-works/pi-ai` / `@earendil-works/pi-coding-agent` 要求 Node.js `>=22.19.0`。版本过低时,`npm install` / `npm ci` 可能跳过这些依赖,随后 `npm run typecheck` 或 `npm run build` 会报告:
|
|
12
|
+
|
|
13
|
+
```text
|
|
14
|
+
Cannot find module '@earendil-works/...'
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
在 Cursor Cloud 中执行安装或验证前,先切换到已配置的 Node.js 22:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
nvm use 22
|
|
21
|
+
node --version
|
|
22
|
+
npm ci
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
当前 VM 预装版本为 v22.22.2;应以 `node --version` 的实际输出为准,不依赖交互式 shell 的默认 PATH。
|
|
26
|
+
|
|
27
|
+
### Git 提交签名导致测试超时
|
|
28
|
+
|
|
29
|
+
Cursor Cloud 的全局 Git 配置可能通过 `cursor-git-ssh-keygen` 为每次提交签名(`gpg.format=ssh`、`commit.gpgsign=true`)。该 helper 可能阻塞 7–30 秒,使会在临时仓库中频繁提交的测试(尤其 `test/worker/delivery/**`、`test/worker/feature/**`)触发 15/30 秒超时。
|
|
30
|
+
|
|
31
|
+
运行 `npm test` 或其他 Git 密集型测试前,可仅对子进程关闭提交签名:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
export GIT_CONFIG_COUNT=1
|
|
35
|
+
export GIT_CONFIG_KEY_0=commit.gpgsign
|
|
36
|
+
export GIT_CONFIG_VALUE_0=false
|
|
37
|
+
npm test
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
也可以只在当前目标仓库设置:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
git config commit.gpgsign false
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
该设置只用于本地开发和测试稳定性,不应写入仓库代码、模板或发布包配置。
|
|
47
|
+
|
|
48
|
+
## 维护规则
|
|
49
|
+
|
|
50
|
+
- 仅把可复现、会反复影响开发或验证的环境问题写入本文。
|
|
51
|
+
- 通用且跨环境成立的要求应写入开发原则或验证矩阵,不应堆在本页。
|
|
52
|
+
- 问题消失或基础镜像修复后,应删除过期说明,避免把临时环境状态长期固化为项目规则。
|
|
@@ -212,7 +212,15 @@
|
|
|
212
212
|
"fromNodeId": {
|
|
213
213
|
"type": "string",
|
|
214
214
|
"pattern": "^[a-z][a-z0-9-]*$",
|
|
215
|
-
"description": "
|
|
215
|
+
"description": "Primary upstream node id whose canonical JSON output in the runner-injected current run directory ($HARNESS_DAG_RUN_DIR/<fromNodeId>.json) supplies the verdict line."
|
|
216
|
+
},
|
|
217
|
+
"fallbackFromNodeIds": {
|
|
218
|
+
"type": "array",
|
|
219
|
+
"items": {
|
|
220
|
+
"type": "string",
|
|
221
|
+
"pattern": "^[a-z][a-z0-9-]*$"
|
|
222
|
+
},
|
|
223
|
+
"description": "Optional fallback upstream node ids tried in order when the primary JSON output is absent, for example when an exclusive condition branch was skipped. Each source must also appear in the gate task depends_on list."
|
|
216
224
|
},
|
|
217
225
|
"accept": {
|
|
218
226
|
"type": "array",
|
|
@@ -242,6 +250,9 @@
|
|
|
242
250
|
"label": { "type": "string", "minLength": 1 }
|
|
243
251
|
}
|
|
244
252
|
},
|
|
253
|
+
"backendTestPipeline": {
|
|
254
|
+
"enum": ["contracts", "semantic-initial", "semantic-final", "execute-parse-initial", "classification-eligibility", "repair-safety-traceability", "finalize-effective-result"]
|
|
255
|
+
},
|
|
245
256
|
"verifyEvidence": { "$ref": "#/$defs/shellVerifyEvidence" },
|
|
246
257
|
"repairArtifactGate": { "$ref": "#/$defs/repairArtifactGate" },
|
|
247
258
|
"envAllowlist": {
|
|
@@ -258,7 +269,8 @@
|
|
|
258
269
|
{ "required": ["commands"], "properties": { "commands": { "minItems": 1 } } },
|
|
259
270
|
{ "required": ["preset"] },
|
|
260
271
|
{ "required": ["verdictGate"] },
|
|
261
|
-
{ "required": ["requirementCoverageGate"] }
|
|
272
|
+
{ "required": ["requirementCoverageGate"] },
|
|
273
|
+
{ "required": ["backendTestPipeline"] }
|
|
262
274
|
]
|
|
263
275
|
},
|
|
264
276
|
"staticConfig": {
|
|
@@ -327,6 +339,17 @@
|
|
|
327
339
|
"items": { "type": "string", "pattern": "^[a-z][a-z0-9-]*$" },
|
|
328
340
|
"default": []
|
|
329
341
|
},
|
|
342
|
+
"dependsPolicy": {
|
|
343
|
+
"type": "string",
|
|
344
|
+
"enum": ["all", "all-or-condition-skip"],
|
|
345
|
+
"default": "all",
|
|
346
|
+
"description": "Dependency join policy. all is fail-closed on any skipped/error dependency. all-or-condition-skip treats only condition-branch skips as soft and runs after all dependencies settle when at least one dependency finished."
|
|
347
|
+
},
|
|
348
|
+
"runIf": {
|
|
349
|
+
"type": "string",
|
|
350
|
+
"minLength": 1,
|
|
351
|
+
"description": "Optional fail-closed node execution condition evaluated after dependencies settle. False records an explicit condition skip; malformed selectors fail the node."
|
|
352
|
+
},
|
|
330
353
|
"complexity": { "$ref": "#/$defs/complexity" },
|
|
331
354
|
"subtask_prompt": {
|
|
332
355
|
"type": "string",
|
|
@@ -366,11 +389,6 @@
|
|
|
366
389
|
"type": "string",
|
|
367
390
|
"minLength": 1
|
|
368
391
|
},
|
|
369
|
-
"firstProtocolLine": {
|
|
370
|
-
"type": "string",
|
|
371
|
-
"minLength": 1,
|
|
372
|
-
"description": "Optional protocol prefix whose first matching Pi assistant-output line is promoted to the canonical first line. Missing matches are not synthesized."
|
|
373
|
-
},
|
|
374
392
|
"allowedPaths": {
|
|
375
393
|
"type": "array",
|
|
376
394
|
"items": { "type": "string", "minLength": 1 },
|
|
@@ -542,11 +542,30 @@
|
|
|
542
542
|
"subtask_prompt_markdown": "./agent-dag-review-verdict.prompt.md"
|
|
543
543
|
},
|
|
544
544
|
{
|
|
545
|
-
"id": "review-
|
|
545
|
+
"id": "review-verdict-recovery-pi",
|
|
546
546
|
"depends_on": [
|
|
547
547
|
"review-pi"
|
|
548
548
|
],
|
|
549
549
|
"complexity": "LOW",
|
|
550
|
+
"executor": "pi",
|
|
551
|
+
"role": "reviewer",
|
|
552
|
+
"writePolicy": "read-only",
|
|
553
|
+
"allowedPaths": [
|
|
554
|
+
"**"
|
|
555
|
+
],
|
|
556
|
+
"forbiddenPaths": [
|
|
557
|
+
".harness/**",
|
|
558
|
+
"artifacts/**"
|
|
559
|
+
],
|
|
560
|
+
"outputContract": "Plain Markdown whose first non-empty line is exactly VERDICT: pass or VERDICT: request-revision, followed by the original review findings without substantive changes. No file writes.",
|
|
561
|
+
"subtask_prompt": "Normalize the output format of review-pi; this is the single read-only format-recovery attempt for the review verdict protocol. Preserve an existing valid VERDICT exactly. Do not invent VERDICT: pass from natural-language approval. If ambiguous, emit VERDICT: request-revision. Do not re-review code or edit files."
|
|
562
|
+
},
|
|
563
|
+
{
|
|
564
|
+
"id": "review-gate-shell",
|
|
565
|
+
"depends_on": [
|
|
566
|
+
"review-verdict-recovery-pi"
|
|
567
|
+
],
|
|
568
|
+
"complexity": "LOW",
|
|
550
569
|
"executor": "shell",
|
|
551
570
|
"role": "verifier",
|
|
552
571
|
"writePolicy": "read-only",
|
|
@@ -557,11 +576,11 @@
|
|
|
557
576
|
".harness/**",
|
|
558
577
|
"artifacts/**"
|
|
559
578
|
],
|
|
560
|
-
"outputContract": "Deterministic review verdict gate: exit 0 only when review-pi first non-empty assistant output line is pass.",
|
|
561
|
-
"subtask_prompt": "Deterministic gate: block decision gate unless review-pi emitted VERDICT: pass.",
|
|
579
|
+
"outputContract": "Deterministic review verdict gate: exit 0 only when review-verdict-recovery-pi first non-empty assistant output line is pass.",
|
|
580
|
+
"subtask_prompt": "Deterministic gate: block decision gate unless review-verdict-recovery-pi emitted VERDICT: pass.",
|
|
562
581
|
"shell": {
|
|
563
582
|
"verdictGate": {
|
|
564
|
-
"fromNodeId": "review-pi",
|
|
583
|
+
"fromNodeId": "review-verdict-recovery-pi",
|
|
565
584
|
"accept": [
|
|
566
585
|
"VERDICT: pass"
|
|
567
586
|
],
|
|
@@ -1,23 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"$id": "https://tea-agent.dev/schemas/backend-test-analysis-
|
|
4
|
-
"title": "Backend Test Analysis
|
|
3
|
+
"$id": "https://tea-agent.dev/schemas/backend-test-analysis-v2.json",
|
|
4
|
+
"title": "Backend Test Analysis v2",
|
|
5
|
+
"description": "The runtime accepts v1 input for compatibility and normalizes it to this v2 artifact.",
|
|
5
6
|
"type": "object",
|
|
6
7
|
"additionalProperties": false,
|
|
7
8
|
"required": ["schemaVersion", "sourceBinding", "acceptanceCriteria", "endpoints", "dataModels", "businessRules", "stateTransitions", "boundaryConstraints", "externalDependencies", "risks", "evidenceGaps"],
|
|
8
9
|
"properties": {
|
|
9
|
-
"schemaVersion": { "const":
|
|
10
|
-
"sourceBinding": {
|
|
11
|
-
"type": "object", "additionalProperties": false,
|
|
12
|
-
"required": ["taskId", "requirementPath", "requirementSha256", "referencePaths", "requirementIds"],
|
|
13
|
-
"properties": {
|
|
14
|
-
"taskId": { "type": "string", "minLength": 1 },
|
|
15
|
-
"requirementPath": { "type": "string", "minLength": 1 },
|
|
16
|
-
"requirementSha256": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
|
|
17
|
-
"referencePaths": { "type": "array", "items": { "type": "string", "minLength": 1 } },
|
|
18
|
-
"requirementIds": { "type": "array", "items": { "type": "string", "pattern": "^(REQ|BR|AC)-[A-Z0-9]+(-[A-Z0-9]+)*$" } }
|
|
19
|
-
}
|
|
20
|
-
},
|
|
10
|
+
"schemaVersion": { "const": 2 },
|
|
11
|
+
"sourceBinding": { "$ref": "#/$defs/sourceBinding" },
|
|
21
12
|
"acceptanceCriteria": { "type": "array", "items": { "$ref": "#/$defs/acceptanceCriterion" } },
|
|
22
13
|
"endpoints": { "type": "array", "items": { "$ref": "#/$defs/endpoint" } },
|
|
23
14
|
"dataModels": { "type": "array", "items": { "$ref": "#/$defs/evidencedDescription" } },
|
|
@@ -30,10 +21,12 @@
|
|
|
30
21
|
},
|
|
31
22
|
"$defs": {
|
|
32
23
|
"sourceRef": { "type": "string", "minLength": 1 },
|
|
33
|
-
"
|
|
24
|
+
"sourceBinding": { "type": "object", "additionalProperties": false, "required": ["taskId", "requirementPath", "requirementSha256", "referencePaths", "requirementIds"], "properties": { "taskId": { "type": "string", "minLength": 1 }, "requirementPath": { "type": "string", "minLength": 1 }, "requirementSha256": { "type": "string", "pattern": "^[a-f0-9]{64}$" }, "referencePaths": { "type": "array", "items": { "type": "string", "minLength": 1 } }, "requirementIds": { "type": "array", "items": { "type": "string", "pattern": "^(REQ|BR|AC)-[A-Z0-9]+(-[A-Z0-9]+)*$" } } } },
|
|
25
|
+
"field": { "type": "object", "additionalProperties": false, "required": ["name", "sourceRefs"], "properties": { "name": { "type": "string", "minLength": 1 }, "type": { "type": "string", "minLength": 1 }, "required": { "type": "boolean" }, "description": { "type": "string" }, "format": { "type": "string", "minLength": 1 }, "comparison": { "enum": ["exact", "parseable-only", "semantic"] }, "precision": { "type": "string", "minLength": 1 }, "sourceRefs": { "type": "array", "items": { "$ref": "#/$defs/sourceRef" } } } },
|
|
26
|
+
"responseBody": { "type": "object", "additionalProperties": false, "required": ["kind", "ordering"], "properties": { "kind": { "enum": ["array", "object", "scalar", "empty", "unknown"] }, "itemSchemaRef": { "type": "string", "minLength": 1 }, "ordering": { "enum": ["specified", "unspecified", "not-applicable"] }, "description": { "type": "string", "minLength": 1 } } },
|
|
34
27
|
"errorCase": { "type": "object", "additionalProperties": false, "required": ["description"], "properties": { "status": { "type": "integer", "minimum": 400, "maximum": 599 }, "code": { "type": "string", "minLength": 1 }, "messageField": { "type": "string", "minLength": 1 }, "description": { "type": "string", "minLength": 1 } } },
|
|
35
28
|
"acceptanceCriterion": { "type": "object", "additionalProperties": false, "required": ["id", "text", "sourceRef"], "properties": { "id": { "type": "string", "pattern": "^AC-[A-Z0-9]+(-[A-Z0-9]+)*$" }, "text": { "type": "string", "minLength": 1 }, "sourceRef": { "$ref": "#/$defs/sourceRef" } } },
|
|
36
|
-
"endpoint": { "type": "object", "additionalProperties": false, "required": ["id", "method", "path", "requestFields", "responseFields", "successStatuses", "errorCases"], "properties": { "id": { "type": "string", "minLength": 1 }, "method": { "enum": ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"] }, "path": { "type": "string", "pattern": "^/" }, "requestFields": { "type": "array", "items": { "$ref": "#/$defs/field" } }, "responseFields": { "type": "array", "items": { "$ref": "#/$defs/field" } }, "successStatuses": { "type": "array", "items": { "type": "integer", "minimum": 100, "maximum": 399 } }, "errorCases": { "type": "array", "items": { "$ref": "#/$defs/errorCase" } } } },
|
|
29
|
+
"endpoint": { "type": "object", "additionalProperties": false, "required": ["id", "method", "path", "requestFields", "responseFields", "responseBody", "successStatuses", "errorCases", "sourceRefs"], "properties": { "id": { "type": "string", "minLength": 1 }, "method": { "enum": ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"] }, "path": { "type": "string", "pattern": "^/" }, "requestFields": { "type": "array", "items": { "$ref": "#/$defs/field" } }, "responseFields": { "type": "array", "items": { "$ref": "#/$defs/field" } }, "responseBody": { "$ref": "#/$defs/responseBody" }, "successStatuses": { "type": "array", "items": { "type": "integer", "minimum": 100, "maximum": 399 } }, "errorCases": { "type": "array", "items": { "$ref": "#/$defs/errorCase" } }, "sourceRefs": { "type": "array", "items": { "$ref": "#/$defs/sourceRef" } } } },
|
|
37
30
|
"evidencedDescription": { "type": "object", "additionalProperties": false, "required": ["id", "description", "sourceRef"], "properties": { "id": { "type": "string", "minLength": 1 }, "description": { "type": "string", "minLength": 1 }, "sourceRef": { "$ref": "#/$defs/sourceRef" } } },
|
|
38
31
|
"businessRule": { "type": "object", "additionalProperties": false, "required": ["id", "text", "sourceRef"], "properties": { "id": { "type": "string", "minLength": 1 }, "text": { "type": "string", "minLength": 1 }, "sourceRef": { "$ref": "#/$defs/sourceRef" } } },
|
|
39
32
|
"stateTransition": { "type": "object", "additionalProperties": false, "required": ["from", "to", "trigger", "sourceRef"], "properties": { "from": { "type": "string", "minLength": 1 }, "to": { "type": "string", "minLength": 1 }, "trigger": { "type": "string", "minLength": 1 }, "sourceRef": { "$ref": "#/$defs/sourceRef" } } },
|