@tea-agent/loop-agent 0.2.0 → 0.2.1

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 (58) hide show
  1. package/AGENTS.md +62 -45
  2. package/CHANGELOG.md +59 -43
  3. package/README.md +180 -179
  4. package/bin/loop-agent.js +21 -21
  5. package/dist/application/dag/generate-task-dag.js +3 -3
  6. package/dist/application/dag/run-dag.js +14 -1
  7. package/dist/application/dag/validate-dag.js +1 -0
  8. package/dist/commands/init.js +482 -459
  9. package/dist/workflows/dag/failure-routing.js +82 -0
  10. package/dist/workflows/dag/lifecycle.js +95 -3
  11. package/dist/workflows/dag/report.js +73 -1
  12. package/docs/README.md +47 -45
  13. package/docs/agent-dag-recovery-playbook.md +32 -6
  14. package/docs/agent-dag-runner.md +17 -17
  15. package/docs/architecture/runtime-boundaries.md +1 -1
  16. package/docs/cursor-executor-usage.md +5 -5
  17. package/docs/decisions/README.md +2 -2
  18. package/docs/design/README.md +24 -24
  19. package/docs/development-principles.md +50 -50
  20. package/docs/dynamic-workflow-dag-engine-roadmap.md +6 -6
  21. package/docs/exec-plans/README.md +4 -4
  22. package/docs/exec-plans/active/README.md +10 -9
  23. package/docs/exec-plans/completed/README.md +8 -8
  24. package/docs/feature-workflow.md +111 -109
  25. package/docs/harness-methodology-verification.md +18 -18
  26. package/docs/loop-agent-harness.md +36 -36
  27. package/docs/production-readiness.md +96 -0
  28. package/docs/progress/README.md +2 -2
  29. package/docs/reports/README.md +4 -2
  30. package/docs/templates/agent-dag-decision-gate-dogfood-report.md +1 -1
  31. package/docs/templates/agent-dag-process-supervisor.prompt.md +2 -2
  32. package/docs/templates/agent-dag-report.schema.json +33 -2
  33. package/docs/templates/agent-dag-review-verdict.prompt.md +1 -1
  34. package/docs/templates/agent-dag.base.json +195 -195
  35. package/docs/templates/agent-dag.final-verification.json +190 -190
  36. package/docs/templates/agent-dag.schema.json +17 -17
  37. package/docs/templates/agent-dag.supervised-implementation.json +500 -500
  38. package/docs/templates/hybrid-dag.json +193 -193
  39. package/docs/templates/production-readiness-checklist.md +57 -0
  40. package/docs/templates/progress-log.md +7 -7
  41. package/docs/templates/project-start-checklist.md +8 -8
  42. package/docs/templates/qa-report.md +17 -11
  43. package/docs/templates/sprint-contract.md +19 -19
  44. package/docs/verification-matrix.md +37 -26
  45. package/examples/example-dag.json +51 -51
  46. package/examples/hybrid-loop-agent-dag.json +194 -194
  47. package/harness.json +5 -5
  48. package/package.json +62 -62
  49. package/skills/loop-agent/SKILL.md +35 -35
  50. package/skills/loop-agent/references/command-reference.md +107 -65
  51. package/skills/loop-agent/references/harness-policy.md +30 -30
  52. package/skills/loop-agent/references/hybrid-dag.md +30 -30
  53. package/skills/loop-agent/references/model-routing.md +1 -1
  54. package/skills/loop-agent/references/orchestrator-and-interventions.md +1 -1
  55. package/skills/loop-agent/references/pi-prompt.md +9 -9
  56. package/skills/loop-agent/references/post-implementation-and-patterns.md +7 -7
  57. package/skills/loop-agent/references/task-workflow.md +19 -19
  58. package/skills/loop-agent/references/verification-and-failure-handling.md +36 -0
@@ -0,0 +1,82 @@
1
+ export const dagProductLineFailureCategoryValues = [
2
+ "SpecUnclear",
3
+ "ContractMismatch",
4
+ "ProductBug",
5
+ "TestBug",
6
+ "EnvFailure",
7
+ "FlakyTest",
8
+ "RiskyChange",
9
+ "DependencyFailure",
10
+ "NeedsHuman",
11
+ "Unknown",
12
+ ];
13
+ const FOLLOW_UP_BY_PRODUCT_LINE = {
14
+ SpecUnclear: "spec-clarification",
15
+ ContractMismatch: "architecture-contract-fix",
16
+ ProductBug: "dev-fix",
17
+ TestBug: "qa-fix-test",
18
+ EnvFailure: "env-fix or retry verify",
19
+ FlakyTest: "flaky-test-analysis",
20
+ RiskyChange: "human-review or architecture-review",
21
+ DependencyFailure: "unblock dependency",
22
+ NeedsHuman: "human-review",
23
+ Unknown: "human triage",
24
+ };
25
+ function routeToProductLine(input) {
26
+ const normalized = input.normalizedFailureCategory;
27
+ if (!normalized || normalized === "success")
28
+ return undefined;
29
+ const raw = input.rawFailureCategory?.toLowerCase() ?? "";
30
+ const nodeId = input.nodeId?.toLowerCase() ?? "";
31
+ switch (normalized) {
32
+ case "write-guard":
33
+ return "RiskyChange";
34
+ case "auth":
35
+ case "executor":
36
+ case "timeout":
37
+ return "EnvFailure";
38
+ case "human-required":
39
+ case "human-rejected":
40
+ case "decision-envelope":
41
+ return "NeedsHuman";
42
+ case "skipped":
43
+ return "DependencyFailure";
44
+ case "shell-command":
45
+ if (raw.includes("flaky"))
46
+ return "FlakyTest";
47
+ if (nodeId.includes("test") || raw.includes("test-bug")) {
48
+ return "TestBug";
49
+ }
50
+ return "ProductBug";
51
+ case "static-error":
52
+ return "SpecUnclear";
53
+ case "validation":
54
+ if (raw.includes("path") ||
55
+ raw.includes("write") ||
56
+ raw.includes("forbidden")) {
57
+ return "RiskyChange";
58
+ }
59
+ if (raw.includes("test-bug") || nodeId.includes("test")) {
60
+ return "TestBug";
61
+ }
62
+ if (raw.includes("test-failure") || raw.includes("verify-failure")) {
63
+ return "ProductBug";
64
+ }
65
+ return "SpecUnclear";
66
+ case "unknown":
67
+ return "Unknown";
68
+ default: {
69
+ const exhaustive = normalized;
70
+ return exhaustive;
71
+ }
72
+ }
73
+ }
74
+ export function routeDagFailure(input) {
75
+ const productLineFailureCategory = routeToProductLine(input);
76
+ if (!productLineFailureCategory)
77
+ return {};
78
+ return {
79
+ productLineFailureCategory,
80
+ recommendedFollowUp: FOLLOW_UP_BY_PRODUCT_LINE[productLineFailureCategory],
81
+ };
82
+ }
@@ -2,6 +2,8 @@ import { access, mkdir, readFile, readdir, rename, } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { writeJsonAtomic, } from "../../infrastructure/harness/atomic-write.js";
4
4
  import { parseDagSpec } from "./types.js";
5
+ import { normalizeDagFailureCategory, } from "./failure-category.js";
6
+ import { routeDagFailure } from "./failure-routing.js";
5
7
  const DAG_LIFECYCLE_SCAN_ORDER = [
6
8
  "paused",
7
9
  "active",
@@ -470,16 +472,106 @@ export async function runDagStatus(repoRoot, rawArgs) {
470
472
  console.log(JSON.stringify(report, null, 2));
471
473
  }
472
474
  export function parseDagDoctorArgs(args) {
473
- for (const arg of args) {
475
+ let runId;
476
+ let markdown = false;
477
+ for (let i = 0; i < args.length; i += 1) {
478
+ const arg = args[i];
479
+ if (arg === "--run-id") {
480
+ runId = args[++i];
481
+ if (!runId || runId.startsWith("-")) {
482
+ throw new Error("dag doctor --run-id requires a value");
483
+ }
484
+ continue;
485
+ }
486
+ if (arg.startsWith("--run-id=")) {
487
+ runId = arg.slice("--run-id=".length);
488
+ if (!runId)
489
+ throw new Error("dag doctor --run-id requires a value");
490
+ continue;
491
+ }
492
+ if (arg === "--markdown") {
493
+ markdown = true;
494
+ continue;
495
+ }
474
496
  if (arg.startsWith("-")) {
475
497
  throw new Error(`unknown dag doctor flag: ${arg}`);
476
498
  }
477
499
  throw new Error(`unexpected positional argument: ${arg}`);
478
500
  }
479
- return {};
501
+ return { runId, markdown };
502
+ }
503
+ function findDoctorFailureNode(state) {
504
+ if (state.pausedByNodeId) {
505
+ const node = state.nodes[state.pausedByNodeId];
506
+ return {
507
+ nodeId: state.pausedByNodeId,
508
+ status: node?.status,
509
+ rawFailureCategory: node?.failureCategory,
510
+ };
511
+ }
512
+ const errorEntry = Object.entries(state.nodes).find(([, node]) => node.status === "ERROR");
513
+ const skippedEntry = Object.entries(state.nodes).find(([, node]) => node.status === "SKIPPED");
514
+ const selected = errorEntry ?? skippedEntry;
515
+ if (!selected) {
516
+ return {
517
+ rawFailureCategory: state.failureCategory,
518
+ status: state.status,
519
+ };
520
+ }
521
+ return {
522
+ nodeId: selected[0],
523
+ status: selected[1].status,
524
+ rawFailureCategory: selected[1].failureCategory,
525
+ };
526
+ }
527
+ async function formatDagDoctorMarkdown(repoRoot, runId) {
528
+ const located = await locateDagRun(repoRoot, runId);
529
+ if (!located) {
530
+ throw new Error(`dag run not found: ${runId}`);
531
+ }
532
+ const state = await readDagRunState(located.runDir);
533
+ const summary = await buildDagStatusReport(repoRoot, runId);
534
+ const failure = findDoctorFailureNode(state);
535
+ const rawFailureCategory = failure.rawFailureCategory ??
536
+ (state.status === "paused" ? "human-required" : state.failureCategory);
537
+ const failureStatus = state.status === "paused" ? "paused" : failure.status;
538
+ const normalizedCategory = normalizeDagFailureCategory(rawFailureCategory, failureStatus ?? state.status);
539
+ const routing = routeDagFailure({
540
+ rawFailureCategory,
541
+ normalizedFailureCategory: normalizedCategory,
542
+ nodeId: failure.nodeId,
543
+ });
544
+ const evidence = failure.nodeId
545
+ ? path.join(located.runDir, failure.nodeId, "result.summary.md")
546
+ : path.join(located.runDir, "state.json");
547
+ const nextCommand = summary.nextRecommendedAction ||
548
+ routing.recommendedFollowUp ||
549
+ "Inspect run facts and choose a recovery path.";
550
+ return [
551
+ "## Diagnosis",
552
+ "",
553
+ `- run id: ${runId}`,
554
+ `- lifecycle: ${located.lifecycle}`,
555
+ `- failed node: ${failure.nodeId ?? "-"}`,
556
+ `- raw failure: ${rawFailureCategory ?? "-"}`,
557
+ `- normalized category: ${normalizedCategory}`,
558
+ `- product-line category: ${routing.productLineFailureCategory ?? "-"}`,
559
+ `- recommended follow-up: ${routing.recommendedFollowUp ?? "-"}`,
560
+ `- evidence: ${evidence}`,
561
+ `- next command: ${nextCommand}`,
562
+ "",
563
+ ].join("\n");
480
564
  }
481
565
  export async function runDagDoctor(repoRoot, rawArgs) {
482
- parseDagDoctorArgs(rawArgs);
566
+ const parsed = parseDagDoctorArgs(rawArgs);
567
+ if (parsed.runId) {
568
+ if (parsed.markdown) {
569
+ console.log(await formatDagDoctorMarkdown(repoRoot, parsed.runId));
570
+ return;
571
+ }
572
+ console.log(JSON.stringify(await buildDagStatusReport(repoRoot, parsed.runId), null, 2));
573
+ return;
574
+ }
483
575
  const report = await buildDagDoctorReport(repoRoot);
484
576
  console.log(JSON.stringify(report, null, 2));
485
577
  }
@@ -7,6 +7,7 @@ import { repairArtifactSchema } from "./repair-artifact.js";
7
7
  import { dagRunDirExists, getDagRunDir, locateDagRun, readDagRunSpec, readDagRunState, } from "./lifecycle.js";
8
8
  export const DAG_CLOSEOUT_DRAFT_DISCLAIMER = "> **Advisory only.** Derived from completed run facts. Canonical source remains `dag report --json` and `.harness/dag-runs/completed/<run-id>/`. Do not treat this draft as authoritative.";
9
9
  import { dagNormalizedFailureCategorySchema, normalizeDagFailureCategory, } from "./failure-category.js";
10
+ import { dagProductLineFailureCategoryValues, routeDagFailure, } from "./failure-routing.js";
10
11
  import { DAG_RECOVERY_ACTIONS, planDagRecovery, } from "./recovery-recommendation.js";
11
12
  import { dagNodeExecutorSchema, dagNodeStatusSchema, LEGACY_TOP_LEVEL_MODELS_ERROR, parseDagSpec, resolveModelForTask, } from "./types.js";
12
13
  export const DAG_REPORT_SCHEMA_VERSION = 1;
@@ -21,6 +22,7 @@ const dagRunStatusSchema = z.enum([
21
22
  "paused",
22
23
  ]);
23
24
  const dagRecoveryActionSchema = z.enum(DAG_RECOVERY_ACTIONS);
25
+ const dagProductLineFailureCategorySchema = z.enum(dagProductLineFailureCategoryValues);
24
26
  const dagArtifactRefSchema = z
25
27
  .object({
26
28
  path: z.string(),
@@ -44,6 +46,8 @@ const dagReportPrimaryFailureSchema = z
44
46
  nodeStatus: dagNodeStatusSchema.optional(),
45
47
  failureCategory: z.string().optional(),
46
48
  normalizedFailureCategory: dagNormalizedFailureCategorySchema.optional(),
49
+ productLineFailureCategory: dagProductLineFailureCategorySchema.optional(),
50
+ recommendedFollowUp: z.string().optional(),
47
51
  })
48
52
  .strict();
49
53
  const dagReportDownstreamSkippedNodeSchema = z
@@ -51,6 +55,8 @@ const dagReportDownstreamSkippedNodeSchema = z
51
55
  nodeId: z.string(),
52
56
  failureCategory: z.string().optional(),
53
57
  normalizedFailureCategory: dagNormalizedFailureCategorySchema.optional(),
58
+ productLineFailureCategory: dagProductLineFailureCategorySchema.optional(),
59
+ recommendedFollowUp: z.string().optional(),
54
60
  })
55
61
  .strict();
56
62
  const dagConvergencePassArtifactRefSchema = z
@@ -130,6 +136,8 @@ const dagNodeReportRowSchema = z
130
136
  tokensUsed: z.number().nonnegative().optional(),
131
137
  failureCategory: z.string().optional(),
132
138
  normalizedFailureCategory: dagNormalizedFailureCategorySchema.optional(),
139
+ productLineFailureCategory: dagProductLineFailureCategorySchema.optional(),
140
+ recommendedFollowUp: z.string().optional(),
133
141
  recoveryRecommendation: dagRecoveryRecommendationSchema.optional(),
134
142
  backend: z.enum(["sdk", "cli"]).optional(),
135
143
  sdkAttempted: z.boolean().optional(),
@@ -150,6 +158,8 @@ const dagRunReportEntrySchema = z
150
158
  finishedAt: z.string().optional(),
151
159
  failureCategory: z.string().optional(),
152
160
  normalizedFailureCategory: dagNormalizedFailureCategorySchema.optional(),
161
+ productLineFailureCategory: dagProductLineFailureCategorySchema.optional(),
162
+ recommendedFollowUp: z.string().optional(),
153
163
  recoveryRecommendation: dagRecoveryRecommendationSchema.optional(),
154
164
  primaryFailure: dagReportPrimaryFailureSchema,
155
165
  primaryRecovery: dagRecoveryRecommendationSchema,
@@ -250,12 +260,18 @@ function buildPrimaryFailure(run, primaryNode) {
250
260
  nodeStatus: primaryNode.status,
251
261
  failureCategory: primaryNode.failureCategory,
252
262
  normalizedFailureCategory: primaryNode.normalizedFailureCategory,
263
+ productLineFailureCategory: primaryNode.productLineFailureCategory,
264
+ recommendedFollowUp: primaryNode.recommendedFollowUp,
253
265
  };
254
266
  }
255
267
  return {
256
268
  scope: "run",
257
269
  failureCategory: run.failureCategory,
258
270
  normalizedFailureCategory: run.normalizedFailureCategory,
271
+ ...routeDagFailure({
272
+ rawFailureCategory: run.failureCategory,
273
+ normalizedFailureCategory: run.normalizedFailureCategory,
274
+ }),
259
275
  };
260
276
  }
261
277
  function resolvePrimaryRecovery(run, primaryNode) {
@@ -302,6 +318,8 @@ function collectDownstreamSkippedNodes(nodes, spec, primaryNode) {
302
318
  nodeId: node.nodeId,
303
319
  failureCategory: node.failureCategory,
304
320
  normalizedFailureCategory: node.normalizedFailureCategory,
321
+ productLineFailureCategory: node.productLineFailureCategory,
322
+ recommendedFollowUp: node.recommendedFollowUp,
305
323
  }));
306
324
  }
307
325
  function isFailedReportRun(run) {
@@ -342,6 +360,12 @@ export async function buildDagRunReportEntry(input) {
342
360
  continue;
343
361
  const task = tasks.get(nodeId);
344
362
  const normalizedFailureCategory = normalizeDagFailureCategory(node.failureCategory, node.status);
363
+ const failureRouting = routeDagFailure({
364
+ rawFailureCategory: node.failureCategory,
365
+ normalizedFailureCategory,
366
+ nodeId,
367
+ executor: node.executor,
368
+ });
345
369
  nodes.push({
346
370
  nodeId,
347
371
  rank,
@@ -355,6 +379,7 @@ export async function buildDagRunReportEntry(input) {
355
379
  tokensUsed: node.tokensUsed,
356
380
  failureCategory: node.failureCategory,
357
381
  normalizedFailureCategory,
382
+ ...failureRouting,
358
383
  recoveryRecommendation: planDagRecovery({
359
384
  status: node.status,
360
385
  normalizedFailureCategory,
@@ -406,6 +431,11 @@ export async function buildDagRunReportEntry(input) {
406
431
  lifecycle: input.lifecycle,
407
432
  runStatus: input.state.status,
408
433
  });
434
+ const runFailureRouting = routeDagFailure({
435
+ rawFailureCategory: runRecoverySource.failureCategory,
436
+ normalizedFailureCategory: runRecoverySource.normalizedFailureCategory,
437
+ nodeId: "nodeId" in runRecoverySource ? runRecoverySource.nodeId : undefined,
438
+ });
409
439
  const partialEntry = {
410
440
  runId: input.state.runId,
411
441
  title: input.state.title,
@@ -416,6 +446,7 @@ export async function buildDagRunReportEntry(input) {
416
446
  finishedAt: input.state.finishedAt,
417
447
  failureCategory: input.state.failureCategory,
418
448
  normalizedFailureCategory,
449
+ ...runFailureRouting,
419
450
  recoveryRecommendation,
420
451
  pausedByNodeId: input.state.pausedByNodeId,
421
452
  pauseReason: input.state.pauseReason,
@@ -638,11 +669,15 @@ function formatPrimaryFailureSection(run) {
638
669
  return [
639
670
  `- **Node**: ${failure.nodeId} (${failure.nodeStatus ?? "unknown"})`,
640
671
  `- **Category (raw/normalized)**: ${failure.failureCategory ?? "-"} / ${failure.normalizedFailureCategory ?? "-"}`,
672
+ `- **Product-line category**: ${failure.productLineFailureCategory ?? "-"}`,
673
+ `- **Recommended follow-up**: ${failure.recommendedFollowUp ?? "-"}`,
641
674
  ];
642
675
  }
643
676
  return [
644
677
  `- **Scope**: run`,
645
678
  `- **Category (raw/normalized)**: ${failure.failureCategory ?? "-"} / ${failure.normalizedFailureCategory ?? "-"}`,
679
+ `- **Product-line category**: ${failure.productLineFailureCategory ?? "-"}`,
680
+ `- **Recommended follow-up**: ${failure.recommendedFollowUp ?? "-"}`,
646
681
  ];
647
682
  }
648
683
  function formatPrimaryRecoverySection(run) {
@@ -784,7 +819,7 @@ export function formatDagReportHandoffMarkdown(report) {
784
819
  ? `- **Failure (raw/normalized)**: ${node.failureCategory} / ${node.normalizedFailureCategory ?? "-"}`
785
820
  : "", `- **Executor**: ${node.executor}${node.backend ? ` (backend: ${node.backend})` : ""}`, `- **Model**: ${node.model ?? "—"}`, `- **Duration**: ${formatDuration(node.durationMs)}`, `- **Tokens**: ${formatTokens(node.tokensUsed)}`, `- **Started**: ${formatTimestamp(node.startedAt)}`, `- **Finished**: ${formatTimestamp(node.finishedAt)}`, "");
786
821
  }
787
- lines.push("## Failures", ...formatFailureSection(run), "", "## Recovery Plan", ...formatRecoveryPlanSection(run), "", "## Artifacts", ...formatArtifactsSection(run), "", "## Suggested Next Action", `- ${suggestedNextAction(run)}`, "");
822
+ lines.push("## Failures", ...formatFailureSection(run), "", "## Recovery Plan", ...formatRecoveryPlanSection(run), "", "## Operator Next Steps", ...formatRecommendedOperatorAction(run), "", "## Artifacts", ...formatArtifactsSection(run), "", "## Suggested Next Action", `- ${suggestedNextAction(run)}`, "");
788
823
  return lines
789
824
  .filter((line) => line !== undefined)
790
825
  .join("\n");
@@ -869,6 +904,8 @@ function formatCloseoutPrimaryFailureSection(run) {
869
904
  `- **Scope**: node`,
870
905
  `- **Node**: ${failure.nodeId} (${failure.nodeStatus ?? "unknown"})`,
871
906
  `- **Category (raw/normalized)**: ${failure.failureCategory ?? "-"} / ${failure.normalizedFailureCategory ?? "-"}`,
907
+ `- **Product-line category**: ${failure.productLineFailureCategory ?? "-"}`,
908
+ `- **Recommended follow-up**: ${failure.recommendedFollowUp ?? "-"}`,
872
909
  ];
873
910
  }
874
911
  if (run.primaryRecovery.action === "none" &&
@@ -879,6 +916,8 @@ function formatCloseoutPrimaryFailureSection(run) {
879
916
  return [
880
917
  `- **Scope**: ${failure.scope}`,
881
918
  `- **Category (raw/normalized)**: ${failure.failureCategory ?? "-"} / ${failure.normalizedFailureCategory ?? "-"}`,
919
+ `- **Product-line category**: ${failure.productLineFailureCategory ?? "-"}`,
920
+ `- **Recommended follow-up**: ${failure.recommendedFollowUp ?? "-"}`,
882
921
  ];
883
922
  }
884
923
  function formatCloseoutRecoverySection(run) {
@@ -939,6 +978,39 @@ function formatRemainingRisksSection(run) {
939
978
  return risks;
940
979
  }
941
980
  export function formatDagCloseoutDraftMarkdown(run) {
981
+ if (isFailedReportRun(run)) {
982
+ const lines = [
983
+ `# Failure Handoff: ${run.runId}`,
984
+ "",
985
+ DAG_CLOSEOUT_DRAFT_DISCLAIMER,
986
+ "",
987
+ "## What failed",
988
+ ...formatCloseoutPrimaryFailureSection(run),
989
+ "",
990
+ "## Evidence",
991
+ ...formatCloseoutVerificationEvidence(run),
992
+ "",
993
+ "## Classification",
994
+ `- raw_failure_category: ${run.primaryFailure.failureCategory ?? "-"}`,
995
+ `- dag_normalized_failure_category: ${run.primaryFailure.normalizedFailureCategory ?? "-"}`,
996
+ `- product_line_failure_category: ${run.primaryFailure.productLineFailureCategory ?? "-"}`,
997
+ `- recommended_follow_up: ${run.primaryFailure.recommendedFollowUp ?? "-"}`,
998
+ "",
999
+ "## Recommended follow-up",
1000
+ ...formatCloseoutRecoverySection(run),
1001
+ "",
1002
+ "## Safe retry conditions",
1003
+ `- Retry only after completing \`${run.primaryFailure.recommendedFollowUp ?? run.primaryRecovery.action}\` and preserving the original DAG run facts.`,
1004
+ "- Do not rewrite `.harness/dag-runs/completed/**`; create a new run or task artifact for follow-up evidence.",
1005
+ "",
1006
+ "## Human decision needed",
1007
+ run.primaryRecovery.humanRequired
1008
+ ? "- Yes. Human review is required before retry or promotion."
1009
+ : "- No required human gate was derived, but review the failure evidence before retry.",
1010
+ "",
1011
+ ];
1012
+ return lines.join("\n");
1013
+ }
942
1014
  const lines = [
943
1015
  `# DAG Closeout Draft: ${run.runId}`,
944
1016
  "",
package/docs/README.md CHANGED
@@ -1,62 +1,64 @@
1
- # Documentation Index
1
+ # 文档索引
2
2
 
3
- `docs/` is the governance root for loop-agent. It contains workflow rules, methodology, verification rules, execution plans, reports, progress logs, decisions, and reusable templates.
3
+ `docs/` loop-agent 的治理根目录,包含工作流规则、方法论、验证规则、执行计划、报告、进度日志、决策记录和可复用模板。
4
4
 
5
- Top-level `AGENTS.md` is the operating map. Durable knowledge belongs here: decisions, contracts, plans, verification evidence, debugging notes, and reusable process rules should be recorded under `docs/` instead of staying only in chat.
5
+ 顶层 `AGENTS.md` 是操作地图。长期知识应落在此处:决策、契约、计划、验证证据、调试笔记和可复用流程规则应记录在 `docs/` 下,而不是只留在聊天里。
6
6
 
7
- ## Core Documents
7
+ ## 核心文档
8
8
 
9
- - `development-principles.md` — repository development principles
10
- - `architecture/runtime-boundaries.md` — runtime layer boundaries and dependency direction
11
- - `feature-workflow.md` — bounded feature workflow
12
- - `verification-matrix.md` — verification command selection
13
- - `loop-agent-harness.md` — runtime and command surface overview
14
- - `agent-dag-runner.md` — Agent DAG runner guide
15
- - `cursor-executor-usage.md` — Cursor executor usage
16
- - `dynamic-workflow-dag-engine-roadmap.md` — Dynamic Workflow DAG Engine roadmap and fit analysis
9
+ - `development-principles.md` — 仓库开发原则
10
+ - `architecture/runtime-boundaries.md` — runtime 层边界与依赖方向
11
+ - `feature-workflow.md` — 有边界的功能工作流
12
+ - `verification-matrix.md` — 验证命令选择
13
+ - `production-readiness.md` — Production Readiness v0.1 范围、证据与 DAG hardening 标准
14
+ - `loop-agent-harness.md` — runtime command surface 概览
15
+ - `agent-dag-runner.md` — Agent DAG runner 指南
16
+ - `cursor-executor-usage.md` — Cursor executor 用法
17
+ - `dynamic-workflow-dag-engine-roadmap.md` — Dynamic Workflow DAG Engine 路线图与适配分析
17
18
 
18
- ## Methodology
19
+ ## 方法论
19
20
 
20
- - `harness-methodology-tdd.md` — TDD discipline for behavior changes and bug fixes
21
- - `harness-methodology-verification.md` — verification discipline before completion claims
22
- - `harness-methodology-debugging.md` — systematic debugging workflow before fixes
21
+ - `harness-methodology-tdd.md` — 行为变更与 bug 修复的 TDD 纪律
22
+ - `harness-methodology-verification.md` — 完成声明前的验证纪律
23
+ - `harness-methodology-debugging.md` — 修复前的系统化调试工作流
23
24
 
24
- ## Artifacts
25
+ ## 产物目录
25
26
 
26
- - `design/README.md` — draft design notes and implementation contracts
27
- - `exec-plans/active/README.md` — active execution plans
28
- - `exec-plans/completed/README.md` — completed execution plans
29
- - `progress/README.md` — progress handoff logs
30
- - `reports/README.md` — verification and audit reports
31
- - `decisions/README.md` — architecture decisions
32
- - `templates/` — reusable planning, reporting, and DAG templates
27
+ - `design/README.md` — 设计草稿与实现契约
28
+ - `exec-plans/active/README.md` — 进行中的执行计划
29
+ - `exec-plans/completed/README.md` — 已完成的执行计划
30
+ - `progress/README.md` — 进度交接日志
31
+ - `reports/README.md` — 验证与审计报告
32
+ - `decisions/README.md` — 架构决策
33
+ - `templates/` — 可复用的规划、报告与 DAG 模板
33
34
 
34
- ## Repository Skills
35
+ ## 仓库 Skills
35
36
 
36
- - `../skills/loop-agent/` — loop-agent's own skill instructions and references.
37
- - Each additional skill uses its own subdirectory under repository-root `../skills/`; these local copies are referenced by DAG templates so maintenance does not depend on external agent skill directories.
37
+ - `../skills/loop-agent/` — loop-agent 自身的 skill 指令与参考资料
38
+ - 每个额外 skill 在仓库根 `../skills/` 下使用独立子目录;这些本地副本由 DAG 模板引用,维护不依赖外部 agent skill 目录
38
39
 
39
- ## Templates
40
+ ## 模板
40
41
 
41
- - `templates/project-start-checklist.md` — pre-work checklist
42
- - `templates/feature-spec.md` — bounded feature specification
43
- - `templates/sprint-contract.md` — implementation contract and acceptance criteria
44
- - `templates/exec-plan.md` — execution plan for non-trivial work
45
- - `templates/progress-log.md` — progress and handoff log
46
- - `templates/qa-report.md` — verification and QA evidence
47
- - `templates/adr.md` — architecture decision record
42
+ - `templates/project-start-checklist.md` — 开工前检查清单
43
+ - `templates/feature-spec.md` — 有边界的功能规格
44
+ - `templates/sprint-contract.md` — 实现契约与验收标准
45
+ - `templates/exec-plan.md` — 非平凡工作的执行计划
46
+ - `templates/progress-log.md` — 进度与交接日志
47
+ - `templates/qa-report.md` — 验证与 QA 证据
48
+ - `templates/production-readiness-checklist.md` — 低/中风险单仓库 DAG readiness 检查清单
49
+ - `templates/adr.md` — 架构决策记录(ADR)
48
50
 
49
- ## Maintenance
51
+ ## 维护
50
52
 
51
- After docs changes, run:
52
-
53
- ```bash
54
- bash scripts/check-repo.sh
55
- ```
56
-
57
- On Windows, run Bash scripts through Git Bash or a configured compatible Bash. Use platform-native paths for actual file operations; reserve `/` for repo refs, JSON/Markdown evidence refs, and glob conventions.
58
-
59
- For a full local gate, run:
53
+ 文档变更后运行:
54
+
55
+ ```bash
56
+ bash scripts/check-repo.sh
57
+ ```
58
+
59
+ Windows 上通过 Git Bash 或已配置的兼容 Bash 运行脚本。实际文件操作使用平台原生路径;`/` 仅用于 repo 引用、JSON/Markdown 证据引用和 glob 约定。
60
+
61
+ 完整本地门禁:
60
62
 
61
63
  ```bash
62
64
  bash scripts/ci.sh
@@ -1,4 +1,4 @@
1
- # Agent DAG Recovery Playbook
1
+ # Agent DAG Recovery Playbook(恢复手册)
2
2
 
3
3
  > **关联**:[`agent-dag-runner.md`](agent-dag-runner.md)(CLI 与 run 语义)· [`templates/agent-dag-decision-gate.prompt.md`](templates/agent-dag-decision-gate.prompt.md)(Decision Gate 消费 recovery 证据)
4
4
 
@@ -6,6 +6,17 @@
6
6
 
7
7
  Agent DAG **recovery planning 是只读、派生、advisory** 的。`dag report` 与 `buildDagDecisionGateEvidence()` 从 `.harness/dag-runs/` 的 canonical facts 聚合 `normalizedFailureCategory` → `recoveryRecommendation`,供人工或 Decision Gate prompt 消费。
8
8
 
9
+ Production Readiness v0.1 在 normalized DAG category 之上增加 product-line routing。Report 与 doctor 输出应保留 raw DAG fact 并派生,不重写已完成 facts:
10
+
11
+ ```text
12
+ raw_failure_category
13
+ dag_normalized_failure_category
14
+ product_line_failure_category
15
+ recommended_follow_up
16
+ ```
17
+
18
+ Product-line taxonomy 定义见 `design/state-and-failure-taxonomy.md`。
19
+
9
20
  **非目标(本 playbook 不覆盖、runner 不实现):**
10
21
 
11
22
  - 自动 retry / resume 节点执行
@@ -47,7 +58,7 @@ npm run dev -- dag decision inspect --run-id <run-id> [--node-id <node-id>]
47
58
  npm run dev -- dag decision validate --run-id <run-id> [--node-id <node-id>]
48
59
  ```
49
60
 
50
- ### Paused run 操作员路径
61
+ ### Paused run operator 路径
51
62
 
52
63
  1. `dag report --paused-latest --json` 或 `dag doctor` — 定位最新 paused run 与 `primaryRecovery`
53
64
  2. `dag status --run-id <id>` — 读 `approvalFlow`、`escalationArtifactPath`、`pendingNodes`
@@ -79,7 +90,22 @@ Decision Gate prompt 侧:`buildDagDecisionGateEvidence()`(`./src/core/dag-de
79
90
  | `inspect-upstream` | 先查上游失败 | SKIPPED 下游节点 |
80
91
  | `unknown` | 未映射类别(不应出现在正常派生路径) | 内部兜底 |
81
92
 
82
- ## 类别 动作 → 操作员指引
93
+ ## Product-Line Routing v0.1
94
+
95
+ | Product-line category | Default follow-up |
96
+ |---|---|
97
+ | `SpecUnclear` | `spec-clarification` |
98
+ | `ContractMismatch` | `architecture-contract-fix` |
99
+ | `ProductBug` | `dev-fix` |
100
+ | `TestBug` | `qa-fix-test` |
101
+ | `EnvFailure` | `env-fix` 或 retry verify |
102
+ | `FlakyTest` | `flaky-test-analysis` |
103
+ | `RiskyChange` | `human-review` / `architecture-review` |
104
+ | `DependencyFailure` | unblock dependency |
105
+ | `NeedsHuman` | `human-review` |
106
+ | `Unknown` | human triage |
107
+
108
+ ## 类别 → 动作 → operator 指引
83
109
 
84
110
  | Normalized category | Recovery action | Operator guidance | Anti-patterns |
85
111
  |---------------------|-----------------|-------------------|---------------|
@@ -107,9 +133,9 @@ Decision Gate prompt 侧:`buildDagDecisionGateEvidence()`(`./src/core/dag-de
107
133
  1. **Primary Failure** — `primaryFailure`(node 或 run scope)
108
134
  2. **Recovery Action** — `primaryRecovery`(action、summary、reason、flags、commandHint)
109
135
  3. **Blocked Downstream / Skipped Nodes** — `downstreamSkippedNodes`
110
- 4. **Recommended Operator Action** — 面向操作员的步骤摘要
136
+ 4. **Recommended Operator Action** — 面向 operator 的步骤摘要
111
137
 
112
- 保存 handoff 时重定向到平台临时目录或 `docs/reports/`,不要写入 `.harness/dag-runs/`。
138
+ 保存 handoff 时重定向到平台临时目录或 `docs/reports/`,不要写入 `.harness/dag-runs/`。
113
139
 
114
140
  ## Decision Gate 消费约定
115
141
 
@@ -123,7 +149,7 @@ Decision Gate prompt 侧:`buildDagDecisionGateEvidence()`(`./src/core/dag-de
123
149
 
124
150
  `dag doctor` 与 `dag status` 通过 `detectDagRunHealthIssues()` 检测 lifecycle 不一致,**不** mutate run facts。
125
151
 
126
- | Code | 典型场景 | 操作员指引 |
152
+ | Code | 典型场景 | operator 指引 |
127
153
  |------|----------|------------|
128
154
  | `terminal-in-active` | run 已完成但 `active/<run-id>/` 残留 | 对照 `completed/` canonical facts;手动 archive 或删除 stale 目录 |
129
155
  | `paused-in-active` | pause 后目录未迁至 `paused/` | `dag doctor` 诊断;修复 facts 后再 approve/resume |
@@ -1,40 +1,40 @@
1
1
  # Agent DAG Runner
2
2
 
3
- Agent DAG is loop-agent's declarative orchestration runtime. A DAG decomposes work into nodes, runs eligible ranks in order, records artifacts, and uses gates for review and verification.
3
+ Agent DAG loop-agent 的声明式编排 runtimeDAG 将工作拆为节点、按序执行 eligible ranks、记录 artifacts,并用 gate review 与验证。
4
4
 
5
- ## Basic Use
5
+ ## 基本用法
6
6
 
7
7
  ```bash
8
- loop-agent dag run-task <task-id> --profile auto --strict-models --output <temp-dir>/<task-id>-dag.json
9
- loop-agent dag validate --dag <temp-dir>/<task-id>-dag.json --strict-models --strict-governance
10
- loop-agent run-dag --dag <temp-dir>/<task-id>-dag.json --cwd .
11
- ```
12
-
13
- `<temp-dir>` is the platform-native temp directory. On Windows, pass native paths for actual `--output`, `--dag`, and `--cwd` values.
8
+ loop-agent dag run-task <task-id> --profile auto --strict-models --output <temp-dir>/<task-id>-dag.json
9
+ loop-agent dag validate --dag <temp-dir>/<task-id>-dag.json --strict-models --strict-governance
10
+ loop-agent run-dag --dag <temp-dir>/<task-id>-dag.json --cwd .
11
+ ```
12
+
13
+ `<temp-dir>` 为平台原生临时目录。Windows `--output`、`--dag`、`--cwd` 的实际值用原生路径。
14
14
 
15
15
  ## Executors
16
16
 
17
- - `static`: deterministic generated artifacts or notes
18
- - `shell`: verification and file-system checks
19
- - `pi`: planning, review, diagnosis, and bounded writing when a node sets `toolProfile: "write"`
20
- - `cursor`: optional bounded write backend when explicitly enabled
17
+ - `static`:确定性生成的 artifacts notes
18
+ - `shell`:验证与文件系统检查
19
+ - `pi`:规划、review、诊断;节点设 `toolProfile: "write"` 时有界写入
20
+ - `cursor`:显式启用时的可选有界写后端
21
21
 
22
22
  ## Skills
23
23
 
24
- DAG specs may declare `defaults.skills`, `skillsByRole`, and node-level `skills`. The runner resolves local instructions from `skills/<skill-name>/SKILL.md` and records resolution metadata in each node's `skills.json` artifact.
24
+ DAG spec 可声明 `defaults.skills`、`skillsByRole` 与节点级 `skills`。Runner `skills/<skill-name>/SKILL.md` 解析本地指令,并在各节点 `skills.json` artifact 中记录解析元数据。
25
25
 
26
- The `loop-agent` skill lives at `skills/loop-agent/SKILL.md`. The legacy root `skill/SKILL.md` path is retained only as a compatibility fallback for older worktrees.
26
+ `loop-agent` skill 位于 `skills/loop-agent/SKILL.md`。遗留根路径 `skill/SKILL.md` 仅为旧 worktree 保留兼容 fallback
27
27
 
28
28
  ## Artifacts
29
29
 
30
- DAG artifacts belong under:
30
+ DAG artifacts 位于:
31
31
 
32
32
  ```text
33
33
  .harness/dag-runs/<state>/<run-id>/artifacts/<node-id>/
34
34
  ```
35
35
 
36
- Root `artifacts/` is not a valid default DAG artifact location.
36
+ 根目录 `artifacts/` 不是有效的默认 DAG artifact 位置。
37
37
 
38
38
  ## Shell Gates
39
39
 
40
- - `shell.verdictGate` reads `$HARNESS_DAG_RUN_DIR/<fromNodeId>.json` from the injected current run directory; 不应自行发现 active run paths.
40
+ - `shell.verdictGate` 从注入的当前 run 目录读取 `$HARNESS_DAG_RUN_DIR/<fromNodeId>.json`;不应自行发现 active run paths
@@ -112,7 +112,7 @@ Runner / Loop ──(迁移中)──> 逐步改为仅经 Store / Appli
112
112
 
113
113
  `scripts/check-architecture-boundaries.sh` 的 transitional allowlist 保持为空。任何新增的 `workflows/executors -> commands` import 必须导致检查 **exit 1**;如果未来确有临时例外,必须先写入 active exec plan,说明移除时间和验证门禁。
114
114
 
115
- ## Governance hooks
115
+ ## Governance 钩子
116
116
 
117
117
  以下脚本由 `scripts/check-repo.sh` 调用(Phase 0 起):
118
118