@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.
Files changed (113) hide show
  1. package/AGENTS.md +4 -8
  2. package/CHANGELOG.md +76 -18
  3. package/README.md +76 -299
  4. package/dist/application/evaluation/alias.js +184 -0
  5. package/dist/application/evaluation/budget.js +192 -0
  6. package/dist/application/evaluation/campaign-hash.js +47 -0
  7. package/dist/application/evaluation/campaign-matrix.js +372 -0
  8. package/dist/application/evaluation/campaign-scorecard.js +135 -0
  9. package/dist/application/evaluation/campaign.js +370 -0
  10. package/dist/application/evaluation/candidate.js +23 -6
  11. package/dist/application/evaluation/corpus-hash.js +38 -0
  12. package/dist/application/evaluation/corpus.js +56 -0
  13. package/dist/application/evaluation/experiment.js +294 -0
  14. package/dist/application/evaluation/ignition.js +198 -0
  15. package/dist/application/evaluation/integrity-audit.js +162 -0
  16. package/dist/application/evaluation/outer-loop.js +132 -0
  17. package/dist/application/evaluation/pi-cell-executor.js +39 -0
  18. package/dist/application/evaluation/private-verifier.js +46 -0
  19. package/dist/application/evaluation/promotion-policy.js +151 -0
  20. package/dist/application/evaluation/proposer.js +98 -0
  21. package/dist/application/evaluation/types.js +522 -0
  22. package/dist/cli/command-definitions.js +19 -3
  23. package/dist/commands/dag-reconcile-run.js +3 -116
  24. package/dist/commands/eval.js +1176 -13
  25. package/dist/commands/init.js +7 -1
  26. package/dist/executors/dag-pi-executor.js +8 -46
  27. package/dist/executors/pi-sdk-executor.js +66 -3
  28. package/dist/executors/shell-executor.js +213 -30
  29. package/dist/executors/shell-presets.js +12 -2
  30. package/dist/executors/shell-write-guard.js +20 -1
  31. package/dist/infrastructure/evaluation/alias-store.js +199 -0
  32. package/dist/infrastructure/evaluation/campaign-store.js +154 -0
  33. package/dist/infrastructure/evaluation/corpus-store.js +181 -0
  34. package/dist/infrastructure/evaluation/experiment-store.js +124 -0
  35. package/dist/infrastructure/evaluation/ignition-store.js +82 -0
  36. package/dist/infrastructure/evaluation/private-verifier-store.js +145 -0
  37. package/dist/infrastructure/evaluation/proposer-store.js +78 -0
  38. package/dist/records/promotion.js +3 -1
  39. package/dist/shared/git-progress.js +9 -2
  40. package/dist/worker/cli.js +83 -0
  41. package/dist/worker/delivery/git-transaction.js +75 -0
  42. package/dist/worker/delivery/verification-bundle.js +13 -2
  43. package/dist/worker/feature/review.js +3 -2
  44. package/dist/worker/observability/read-model.js +56 -0
  45. package/dist/worker/observe/server.js +6 -3
  46. package/dist/worker/observe/static/dag-helpers.js +0 -62
  47. package/dist/worker/observe/static/styles.css +18 -55
  48. package/dist/worker/observe/static/views/dag.js +13 -5
  49. package/dist/worker/outcomes/adapters.js +4 -1
  50. package/dist/worker/outcomes/declared-artifacts.js +103 -0
  51. package/dist/worker/outcomes/evidence-tokens.js +29 -0
  52. package/dist/worker/outcomes/gate.js +10 -11
  53. package/dist/worker/outcomes/projector.js +30 -4
  54. package/dist/worker/outcomes/types.js +3 -0
  55. package/dist/worker/pool/reconcile.js +285 -0
  56. package/dist/worker/run-task/run-task.js +81 -4
  57. package/dist/worker/runner/run-ready.js +25 -2
  58. package/dist/worker/task-graph/ready-planner.js +14 -8
  59. package/dist/worker/task-graph/task-graph-schema.js +5 -3
  60. package/dist/workflows/dag/backend-test-analysis-contract.js +87 -30
  61. package/dist/workflows/dag/backend-test-case-manifest.js +71 -8
  62. package/dist/workflows/dag/backend-test-execution-contract.js +63 -11
  63. package/dist/workflows/dag/backend-test-repair-contract.js +94 -0
  64. package/dist/workflows/dag/backend-test-result-contract.js +6 -4
  65. package/dist/workflows/dag/backend-test-semantic-review-contract.js +36 -0
  66. package/dist/workflows/dag/budget-enforcement.js +67 -0
  67. package/dist/workflows/dag/context-policy.js +137 -0
  68. package/dist/workflows/dag/dynamic-runtime/condition.js +1 -1
  69. package/dist/workflows/dag/dynamic-runtime/shared.js +42 -0
  70. package/dist/workflows/dag/failure-routing.js +8 -1
  71. package/dist/workflows/dag/frontend-implementation-contract.js +32 -93
  72. package/dist/workflows/dag/init-hybrid.js +624 -172
  73. package/dist/workflows/dag/knowledge-curator.js +3 -0
  74. package/dist/workflows/dag/lifecycle.js +33 -2
  75. package/dist/workflows/dag/node-execution.js +11 -4
  76. package/dist/workflows/dag/prompt.js +1 -1
  77. package/dist/workflows/dag/reconcile-run.js +121 -0
  78. package/dist/workflows/dag/report.js +12 -0
  79. package/dist/workflows/dag/runner.js +43 -16
  80. package/dist/workflows/dag/scheduler.js +87 -17
  81. package/dist/workflows/dag/skill-snapshot.js +11 -7
  82. package/dist/workflows/dag/types.js +49 -1
  83. package/dist/workflows/dag/validate.js +35 -15
  84. package/docs/README.md +3 -1
  85. package/docs/architecture/runtime-boundaries.md +3 -2
  86. package/docs/init-surface.manifest.json +4 -0
  87. package/docs/local-development-environment.md +52 -0
  88. package/docs/templates/agent-dag.schema.json +25 -7
  89. package/docs/templates/agent-dag.supervised-implementation.json +23 -4
  90. package/docs/templates/backend-test-analysis.schema.json +9 -16
  91. package/docs/templates/backend-test-dag.json +493 -197
  92. package/docs/templates/backend-test-dag.review-cases.prompt.md +10 -4
  93. package/docs/templates/backend-test-execution.schema.json +6 -1
  94. package/docs/templates/branch-merge-report.md +14 -0
  95. package/docs/templates/evaluation/campaign-budget-v1.json +12 -0
  96. package/docs/templates/evaluation/campaign-dogfood-v0.json +24 -0
  97. package/docs/templates/evaluation/campaign-evidence-v1.json +44 -0
  98. package/docs/templates/evaluation/context-policy-baseline-v1.json +17 -0
  99. package/docs/templates/evaluation/context-policy-role-specialized-v1.json +28 -0
  100. package/docs/templates/evaluation/corpus-dogfood-v0.manifest.json +118 -0
  101. package/docs/templates/evaluation/matrix-dag-dry-run-v1.json +21 -0
  102. package/docs/templates/evaluation/matrix-fixture-v1.json +10 -0
  103. package/docs/templates/evaluation/private-verifier-dogfood-v0.json +16 -0
  104. package/docs/templates/product-line/AGENTS.md +1 -0
  105. package/docs/templates/product-line/README.md +17 -0
  106. package/docs/templates/product-line/acceptance.yaml +9 -0
  107. package/docs/templates/product-line/feature.yaml +11 -0
  108. package/docs/templates/product-line/task-graph.yaml +8 -0
  109. package/docs/templates/product-line/task.yaml +4 -0
  110. package/package.json +2 -1
  111. package/skills/frontend-implementation/references/node-contracts.md +3 -3
  112. package/skills/loop-agent/references/command-reference.md +5 -0
  113. 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
- if (!upstreamIds.has(verdictGate.fromNodeId)) {
141
- issues.push({
142
- type: "invalid-verdict-gate-config",
143
- message: `task ${task.id} shell.verdictGate.fromNodeId "${verdictGate.fromNodeId}" must appear in depends_on`,
144
- });
145
- }
146
- const upstreamTask = spec.tasks.find((candidate) => candidate.id === verdictGate.fromNodeId);
147
- if (upstreamTask && upstreamTask.executor === "shell") {
148
- issues.push({
149
- type: "invalid-verdict-gate-config",
150
- message: `task ${task.id} shell.verdictGate.fromNodeId "${verdictGate.fromNodeId}" must reference a non-shell upstream verdict node`,
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` — 跨分支合并的功能保留、冲突解析、init/update 与 package surface 审计模板
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
- - **允许依赖**:TaskSpec、Task Pool、observability、Node filesystem/path 与明确的 shared/task contract;它不是第二套 executor DAG kernel。
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": "Upstream node id whose canonical JSON output in the runner-injected current run directory ($HARNESS_DAG_RUN_DIR/<fromNodeId>.json) supplies the verdict line."
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-gate-shell",
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-v1.json",
4
- "title": "Backend Test Analysis v1",
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": 1 },
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
- "field": { "type": "object", "additionalProperties": false, "required": ["name"], "properties": { "name": { "type": "string", "minLength": 1 }, "type": { "type": "string", "minLength": 1 }, "required": { "type": "boolean" }, "description": { "type": "string" } } },
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" } } },