@tea-agent/loop-agent 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/AGENTS.md +142 -142
  2. package/CHANGELOG.md +116 -98
  3. package/README.md +195 -195
  4. package/bin/agent-worker.js +22 -22
  5. package/bin/loop-agent.js +21 -21
  6. package/dist/application/dag/args.js +9 -1
  7. package/dist/application/dag/run-dag.js +16 -2
  8. package/dist/cli/command-definitions.js +22 -4
  9. package/dist/cli/help.js +3 -2
  10. package/dist/cli/program.js +7 -5
  11. package/dist/commands/import-prd.js +76 -0
  12. package/dist/commands/init.js +467 -457
  13. package/dist/commands/instructions.js +90 -58
  14. package/dist/commands/loop-benchmark.js +11 -11
  15. package/dist/commands/pi-reuse-benchmark.js +16 -16
  16. package/dist/executors/cursor-executor.js +1 -1
  17. package/dist/executors/dag-pi-executor.js +1 -0
  18. package/dist/executors/pi-sdk-executor.js +63 -1
  19. package/dist/shared/preview.js +39 -0
  20. package/dist/task/runtime.js +27 -27
  21. package/dist/task/source-references.js +221 -0
  22. package/dist/worker/cli.js +62 -1
  23. package/dist/worker/loop-agent/loop-agent-client.js +97 -5
  24. package/dist/worker/materialize/harness-task-materializer.js +162 -5
  25. package/dist/worker/observability/event-store.js +82 -0
  26. package/dist/worker/observability/events.js +79 -0
  27. package/dist/worker/observability/progress-composite.js +33 -0
  28. package/dist/worker/observability/read-model.js +1013 -0
  29. package/dist/worker/observability/snapshot-store.js +43 -0
  30. package/dist/worker/observability/types.js +1 -0
  31. package/dist/worker/observe/paths.js +64 -0
  32. package/dist/worker/observe/routes.js +423 -0
  33. package/dist/worker/observe/server.js +61 -0
  34. package/dist/worker/observe/static/app.js +1419 -0
  35. package/dist/worker/observe/static/index.html +63 -0
  36. package/dist/worker/observe/static/styles.css +613 -0
  37. package/dist/worker/pool/failure-routing.js +41 -6
  38. package/dist/worker/pool/run-store.js +50 -0
  39. package/dist/worker/progress-reporter.js +0 -18
  40. package/dist/worker/run-task/run-task.js +327 -92
  41. package/dist/worker/runner/run-ready.js +112 -4
  42. package/dist/workflows/dag/canvas-observer.js +275 -275
  43. package/dist/workflows/dag/event-observer.js +132 -0
  44. package/dist/workflows/dag/init-hybrid.js +146 -13
  45. package/dist/workflows/dag/observer-compose.js +52 -0
  46. package/docs/README.md +74 -72
  47. package/docs/agent-dag-recovery-playbook.md +184 -184
  48. package/docs/agent-dag-runner.md +42 -42
  49. package/docs/architecture/runtime-boundaries.md +162 -147
  50. package/docs/cursor-executor-usage.md +25 -25
  51. package/docs/decisions/README.md +3 -3
  52. package/docs/design/README.md +49 -36
  53. package/docs/development-principles.md +73 -73
  54. package/docs/dynamic-workflow-dag-engine-roadmap.md +1749 -1749
  55. package/docs/exec-plans/README.md +6 -6
  56. package/docs/exec-plans/active/README.md +12 -7
  57. package/docs/exec-plans/completed/README.md +31 -19
  58. package/docs/feature-workflow.md +186 -186
  59. package/docs/harness-methodology-debugging.md +153 -153
  60. package/docs/harness-methodology-tdd.md +130 -130
  61. package/docs/harness-methodology-verification.md +27 -27
  62. package/docs/init-surface.manifest.json +205 -199
  63. package/docs/loop-agent-harness.md +55 -42
  64. package/docs/production-readiness.md +96 -96
  65. package/docs/progress/README.md +3 -3
  66. package/docs/reports/README.md +9 -5
  67. package/docs/skills/README.md +6 -6
  68. package/docs/skills/vetted-skill-registry.md +26 -26
  69. package/docs/templates/adr.md +60 -60
  70. package/docs/templates/agent-dag-authority-surface-audit.prompt.md +94 -94
  71. package/docs/templates/agent-dag-decision-envelope.schema.json +213 -213
  72. package/docs/templates/agent-dag-decision-gate-dogfood-report.md +117 -117
  73. package/docs/templates/agent-dag-decision-gate.prompt.md +246 -246
  74. package/docs/templates/agent-dag-process-supervisor.prompt.md +98 -98
  75. package/docs/templates/agent-dag-report.schema.json +454 -454
  76. package/docs/templates/agent-dag-review-verdict.prompt.md +68 -68
  77. package/docs/templates/agent-dag.base.json +195 -195
  78. package/docs/templates/agent-dag.final-verification.json +190 -190
  79. package/docs/templates/agent-dag.schema.json +316 -316
  80. package/docs/templates/agent-dag.supervised-implementation.json +500 -500
  81. package/docs/templates/exec-plan.md +64 -64
  82. package/docs/templates/feature-spec.md +53 -53
  83. package/docs/templates/hybrid-dag.json +193 -193
  84. package/docs/templates/init-evolution-review.md +33 -33
  85. package/docs/templates/production-readiness-checklist.md +57 -57
  86. package/docs/templates/progress-log.md +17 -17
  87. package/docs/templates/project-start-checklist.md +9 -9
  88. package/docs/templates/qa-report.md +48 -48
  89. package/docs/templates/sprint-contract.md +29 -29
  90. package/docs/templates/worker-dogfood-evidence.md +52 -0
  91. package/docs/templates/worker-dogfood-setup.md +48 -0
  92. package/docs/verification-matrix.md +41 -41
  93. package/examples/decision-gate-agent-dag.json +123 -123
  94. package/examples/example-dag.json +51 -51
  95. package/examples/hybrid-loop-agent-dag.json +194 -194
  96. package/harness.json +70 -69
  97. package/package.json +66 -66
  98. package/skills/ai-engineering-context/SKILL.md +48 -48
  99. package/skills/code-review-core/SKILL.md +20 -20
  100. package/skills/codebase-scout/SKILL.md +19 -19
  101. package/skills/init-capability-evolution/SKILL.md +69 -69
  102. package/skills/loop-agent/SKILL.md +149 -147
  103. package/skills/loop-agent/references/README.md +67 -67
  104. package/skills/loop-agent/references/command-reference.md +412 -403
  105. package/skills/loop-agent/references/harness-policy.md +263 -259
  106. package/skills/loop-agent/references/hybrid-dag.md +216 -216
  107. package/skills/loop-agent/references/learned/README.md +21 -21
  108. package/skills/loop-agent/references/long-running-loop.md +59 -59
  109. package/skills/loop-agent/references/model-routing.md +36 -36
  110. package/skills/loop-agent/references/multi-worktree.md +54 -54
  111. package/skills/loop-agent/references/one-shot-runs.md +85 -85
  112. package/skills/loop-agent/references/orchestrator-and-interventions.md +169 -169
  113. package/skills/loop-agent/references/pi-prompt.md +23 -23
  114. package/skills/loop-agent/references/pi-subagent-assisted-mode.md +81 -81
  115. package/skills/loop-agent/references/post-implementation-and-patterns.md +44 -44
  116. package/skills/loop-agent/references/task-workflow.md +89 -84
  117. package/skills/loop-agent/references/verification-and-failure-handling.md +128 -128
  118. package/skills/requesting-code-review/SKILL.md +101 -101
  119. package/skills/requesting-code-review/code-reviewer.md +168 -168
  120. package/skills/systematic-debugging/CREATION-LOG.md +119 -119
  121. package/skills/systematic-debugging/SKILL.md +296 -296
  122. package/skills/systematic-debugging/condition-based-waiting-example.ts +158 -158
  123. package/skills/systematic-debugging/condition-based-waiting.md +115 -115
  124. package/skills/systematic-debugging/defense-in-depth.md +122 -122
  125. package/skills/systematic-debugging/find-polluter.sh +63 -63
  126. package/skills/systematic-debugging/root-cause-tracing.md +169 -169
  127. package/skills/systematic-debugging/test-academic.md +14 -14
  128. package/skills/systematic-debugging/test-pressure-1.md +58 -58
  129. package/skills/systematic-debugging/test-pressure-2.md +68 -68
  130. package/skills/systematic-debugging/test-pressure-3.md +69 -69
  131. package/skills/test-driven-development/SKILL.md +20 -20
  132. package/skills/verification-before-completion/SKILL.md +154 -154
  133. package/skills/webapp-testing/SKILL.md +19 -19
@@ -0,0 +1,132 @@
1
+ import { appendFile, mkdir } from "node:fs/promises";
2
+ import { randomUUID } from "node:crypto";
3
+ import path from "node:path";
4
+ import { redactSecrets, truncateUtf8Preview } from "../../shared/preview.js";
5
+ import { resolveModelForTask, } from "./types.js";
6
+ const DEFAULT_MAX_OUTPUT_PREVIEW_BYTES = 4096;
7
+ /**
8
+ * Truncate `text` to fit within `maxBytes` (UTF-8), appending a marker suffix
9
+ * describing the number of elided bytes. Local implementation to avoid importing
10
+ * worker observability (which would create a circular dependency).
11
+ */
12
+ export function truncateEventPreview(text, maxBytes = DEFAULT_MAX_OUTPUT_PREVIEW_BYTES) {
13
+ return truncateUtf8Preview(text, maxBytes);
14
+ }
15
+ function reportError(context, error) {
16
+ const message = error instanceof Error ? error.message : String(error);
17
+ process.stderr.write(`[dag-event-observer] ${context} failed: ${message}\n`);
18
+ }
19
+ function findTask(spec, state, nodeId) {
20
+ const node = state.nodes[nodeId];
21
+ const task = spec?.tasks.find((candidate) => candidate.id === nodeId);
22
+ return { task, node };
23
+ }
24
+ function rankOf(state, nodeId) {
25
+ for (let i = 0; i < state.ranks.length; i += 1) {
26
+ if (state.ranks[i]?.includes(nodeId))
27
+ return String(i);
28
+ }
29
+ return undefined;
30
+ }
31
+ export function createDagEventObserver(options) {
32
+ const { eventsJsonlPath } = options;
33
+ const maxOutputPreviewBytes = options.maxOutputPreviewBytes ?? DEFAULT_MAX_OUTPUT_PREVIEW_BYTES;
34
+ let writeError = null;
35
+ const append = async (event) => {
36
+ try {
37
+ await mkdir(path.dirname(eventsJsonlPath), { recursive: true });
38
+ const line = `${JSON.stringify(event)}\n`;
39
+ await appendFile(eventsJsonlPath, line, "utf8");
40
+ }
41
+ catch (error) {
42
+ writeError = error;
43
+ reportError(`append ${event.type}`, error);
44
+ }
45
+ };
46
+ const buildBase = (type, state) => ({
47
+ schemaVersion: 1,
48
+ id: randomUUID(),
49
+ at: new Date().toISOString(),
50
+ dagRunId: state.runId || options.dagRunId || "dag",
51
+ });
52
+ const observer = {
53
+ onRunStart: async (state) => {
54
+ const event = {
55
+ ...buildBase("dag.run.started", state),
56
+ type: "dag.run.started",
57
+ status: state.status,
58
+ label: state.title,
59
+ };
60
+ await append(event);
61
+ },
62
+ onNodeStart: async (nodeId, state) => {
63
+ const { task, node } = findTask(options.spec, state, nodeId);
64
+ const event = {
65
+ ...buildBase("dag.node.started", state),
66
+ type: "dag.node.started",
67
+ nodeId,
68
+ status: node?.status,
69
+ rank: rankOf(state, nodeId),
70
+ executor: node?.executor,
71
+ model: task
72
+ ? resolveModelForTask(task, options.spec?.executorModels)
73
+ : undefined,
74
+ label: node?.id,
75
+ };
76
+ await append(event);
77
+ },
78
+ onNodeOutput: async (nodeId, chunk, state) => {
79
+ const { task, node } = findTask(options.spec, state, nodeId);
80
+ const event = {
81
+ ...buildBase("dag.node.output", state),
82
+ type: "dag.node.output",
83
+ nodeId,
84
+ status: node?.status,
85
+ rank: rankOf(state, nodeId),
86
+ executor: node?.executor,
87
+ model: task
88
+ ? resolveModelForTask(task, options.spec?.executorModels)
89
+ : undefined,
90
+ label: node?.id,
91
+ outputPreview: truncateEventPreview(redactSecrets(chunk), maxOutputPreviewBytes),
92
+ };
93
+ await append(event);
94
+ },
95
+ onNodeFinish: async (nodeId, state) => {
96
+ const { task, node } = findTask(options.spec, state, nodeId);
97
+ const event = {
98
+ ...buildBase("dag.node.finished", state),
99
+ type: "dag.node.finished",
100
+ nodeId,
101
+ status: node?.status,
102
+ rank: rankOf(state, nodeId),
103
+ executor: node?.executor,
104
+ model: task
105
+ ? resolveModelForTask(task, options.spec?.executorModels)
106
+ : undefined,
107
+ label: node?.id,
108
+ durationMs: node?.durationMs,
109
+ };
110
+ await append(event);
111
+ },
112
+ onRunFinish: async (state) => {
113
+ const event = {
114
+ ...buildBase("dag.run.finished", state),
115
+ type: "dag.run.finished",
116
+ status: state.status,
117
+ label: state.title,
118
+ };
119
+ await append(event);
120
+ },
121
+ };
122
+ return {
123
+ observer,
124
+ flush: async () => {
125
+ // No buffering: append is immediate. Surface the last write error
126
+ // (best-effort) only if the caller asks, but never throw.
127
+ if (writeError) {
128
+ reportError("flush", writeError);
129
+ }
130
+ },
131
+ };
132
+ }
@@ -1,4 +1,4 @@
1
- import { access, readFile, writeFile } from "node:fs/promises";
1
+ import { access, readdir, readFile, writeFile } from "node:fs/promises";
2
2
  import os from "node:os";
3
3
  import path from "node:path";
4
4
  import { assertValidDagSpec } from "./validate.js";
@@ -9,11 +9,14 @@ import { resolveAdapter } from "../../adapters/index.js";
9
9
  import { loadHarnessManifest } from "../../governance/harness.js";
10
10
  import { buildAuthoritySurfaceAuditNode, buildAuthoritySurfaceGateNode, resolveAuthoritySurfaceAudit, } from "./authority-surface.js";
11
11
  import { getTaskPaths, loadTaskConfig } from "../../task/runtime.js";
12
+ import { materializeTaskReferenceDocs } from "../../task/source-references.js";
12
13
  import { resolveVerifyPreset } from "../../executors/shell-verification.js";
13
14
  import { resolveExecutorModelMatrices } from "../../executors/model-routing.js";
14
15
  const REQUIREMENT_FILE = "需求.md";
15
16
  const CONSTRAINT_FILE = "执行约束.md";
17
+ const REFERENCE_DIRECTORY = "references";
16
18
  const MAX_SOURCE_EXCERPT_CHARS = 2000;
19
+ const MAX_SOURCE_REFERENCE_DOCUMENTS = 8;
17
20
  const HYBRID_DEFAULTS = {
18
21
  executor: "pi",
19
22
  piBackend: "sdk-first",
@@ -101,27 +104,85 @@ function mapTaskComplexity(complexity) {
101
104
  return "HIGH";
102
105
  return "MED";
103
106
  }
104
- function excerptMarkdown(content, maxChars = MAX_SOURCE_EXCERPT_CHARS) {
107
+ export function excerptMarkdown(content, options = {}) {
108
+ const maxChars = options.maxChars ?? MAX_SOURCE_EXCERPT_CHARS;
105
109
  const trimmed = content.trim();
106
- if (trimmed.length <= maxChars)
107
- return trimmed;
108
- return `${trimmed.slice(0, maxChars)}\n\n...[truncated for draft prompt]`;
110
+ if (trimmed.length <= maxChars) {
111
+ return {
112
+ text: trimmed,
113
+ truncated: false,
114
+ originalChars: trimmed.length,
115
+ maxChars,
116
+ };
117
+ }
118
+ const omitted = trimmed.length - maxChars;
119
+ const ref = options.sourceRef?.trim();
120
+ const pointer = ref
121
+ ? [
122
+ `...[truncated for draft prompt: showing first ${maxChars} of ${trimmed.length} chars; omitted ${omitted} chars]`,
123
+ `Full source (authoritative, do not invent missing content): ${ref}`,
124
+ "When the omitted tail may affect acceptance/non-goals/constraints, re-read that file before deciding.",
125
+ ].join("\n")
126
+ : `...[truncated for draft prompt: showing first ${maxChars} of ${trimmed.length} chars; omitted ${omitted} chars]`;
127
+ return {
128
+ text: `${trimmed.slice(0, maxChars)}\n\n${pointer}`,
129
+ truncated: true,
130
+ originalChars: trimmed.length,
131
+ maxChars,
132
+ };
133
+ }
134
+ function isHeadingLine(line) {
135
+ return /^#{1,6}\s+/.test(line.trim());
136
+ }
137
+ function isMetadataLine(line) {
138
+ const trimmed = line.trim();
139
+ if (!trimmed)
140
+ return true;
141
+ if (/^(TaskSpec business id|Feature id|Task type|Risk level)\s*:/i.test(trimmed)) {
142
+ return true;
143
+ }
144
+ if (/^(权威来源|SHA-256|冲突时以)/.test(trimmed))
145
+ return true;
146
+ if (/^>/.test(trimmed) && /(权威来源|SHA-256|原始 PRD|reference)/i.test(trimmed)) {
147
+ return true;
148
+ }
149
+ return false;
109
150
  }
110
- function extractObjective(requirementMarkdown, title) {
151
+ export function extractObjective(requirementMarkdown, title) {
152
+ const sectionMatchers = [
153
+ /##\s*(目标|Objective|Goals?)\s*\n([\s\S]*?)(?=\n##\s|\n#\s|$)/i,
154
+ /##\s*Description\s*\n([\s\S]*?)(?=\n##\s|\n#\s|$)/i,
155
+ ];
156
+ for (const matcher of sectionMatchers) {
157
+ const match = requirementMarkdown.match(matcher);
158
+ if (!match)
159
+ continue;
160
+ const body = (match[2] ?? match[1] ?? "").trim();
161
+ for (const line of body.split("\n")) {
162
+ const trimmed = line
163
+ .replace(/^[-*]\s*\[[ xX]\]\s*/, "")
164
+ .replace(/^[-*]\s*/, "")
165
+ .trim();
166
+ if (!trimmed || isHeadingLine(trimmed) || isMetadataLine(trimmed))
167
+ continue;
168
+ return trimmed.slice(0, 500);
169
+ }
170
+ }
111
171
  const lines = requirementMarkdown.split("\n");
112
172
  for (const line of lines) {
113
173
  const trimmed = line.trim();
114
- if (!trimmed || trimmed.startsWith("#"))
174
+ if (!trimmed || isHeadingLine(trimmed) || isMetadataLine(trimmed))
115
175
  continue;
116
176
  return trimmed.slice(0, 500);
117
177
  }
118
178
  return `Implement task: ${title}`;
119
179
  }
120
- function extractSuccessCriteria(requirementMarkdown, taskId) {
180
+ const SUCCESS_CRITERIA_SECTION = /##\s*(验收标准|完成标准|Acceptance(?:\s+References)?|Success Criteria)\s*\n([\s\S]*?)(?=\n##\s|\n#\s|$)/i;
181
+ export function extractSuccessCriteria(requirementMarkdown, taskId) {
121
182
  const criteria = [];
122
- const sectionMatch = requirementMarkdown.match(/##\s*验收标准\s*\n([\s\S]*?)(?=\n##\s|\n#\s|$)/);
183
+ const sectionMatch = requirementMarkdown.match(SUCCESS_CRITERIA_SECTION);
123
184
  if (sectionMatch) {
124
- for (const line of sectionMatch[1].split("\n")) {
185
+ for (const line of sectionMatch[2].split("\n")) {
125
186
  const trimmed = line.replace(/^[-*]\s*\[[ xX]\]\s*/, "").trim();
126
187
  if (trimmed.startsWith("-") || trimmed.startsWith("*")) {
127
188
  criteria.push(trimmed.replace(/^[-*]\s*/, "").trim());
@@ -226,20 +287,77 @@ function deriveParallelScoutPaths(taskConfig) {
226
287
  : allowed,
227
288
  };
228
289
  }
290
+ function toTaskRelativeSourcePath(sources, absolutePath) {
291
+ return path
292
+ .relative(sources.taskDir, absolutePath)
293
+ .replaceAll(path.sep, "/");
294
+ }
229
295
  function buildSourceContextBlock(sources) {
296
+ const requirementRef = toTaskRelativeSourcePath(sources, sources.requirementPath);
297
+ const requirementExcerpt = excerptMarkdown(sources.requirementMarkdown, {
298
+ sourceRef: requirementRef,
299
+ });
230
300
  const parts = [
231
301
  "## Task source: 需求.md",
232
- excerptMarkdown(sources.requirementMarkdown),
302
+ requirementExcerpt.text,
233
303
  ];
234
304
  if (sources.constraintMarkdown) {
235
- parts.push("## Task source: 执行约束.md", excerptMarkdown(sources.constraintMarkdown));
305
+ const constraintRef = toTaskRelativeSourcePath(sources, sources.constraintPath);
306
+ const constraintExcerpt = excerptMarkdown(sources.constraintMarkdown, {
307
+ sourceRef: constraintRef,
308
+ });
309
+ parts.push("## Task source: 执行约束.md", constraintExcerpt.text);
310
+ }
311
+ for (const reference of sources.referenceDocuments ?? []) {
312
+ const relativePath = path
313
+ .relative(path.join(sources.taskDir, "source"), reference.path)
314
+ .replaceAll(path.sep, "/");
315
+ const referenceRef = toTaskRelativeSourcePath(sources, reference.path);
316
+ const referenceExcerpt = excerptMarkdown(reference.markdown, {
317
+ sourceRef: referenceRef,
318
+ });
319
+ parts.push(`## Task source reference: ${relativePath}`, referenceExcerpt.text);
236
320
  }
237
- parts.push("## Task config summary", `- taskId: ${sources.taskConfig.taskId}`, `- flow: ${sources.taskConfig.flow}`, `- complexity: ${sources.taskConfig.complexity}`, `- contextProfile: ${sources.taskConfig.contextProfile}`, `- allowedPaths: ${sources.taskConfig.allowedPaths.join(", ") || "(none — review before execute)"}`, `- forbiddenPaths: ${sources.taskConfig.forbiddenPaths.join(", ") || "(none)"}`, '- Pi DAG nodes are read-only unless toolProfile="write" is explicitly selected for a bounded writer node.', "- Agent DAG read-only nodes must not write root artifacts/**; root artifacts/ is not a per-node scratchpad.");
321
+ parts.push("## Task config summary", `- taskId: ${sources.taskConfig.taskId}`, `- flow: ${sources.taskConfig.flow}`, `- complexity: ${sources.taskConfig.complexity}`, `- contextProfile: ${sources.taskConfig.contextProfile}`, `- allowedPaths: ${sources.taskConfig.allowedPaths.join(", ") || "(none — review before execute)"}`, `- forbiddenPaths: ${sources.taskConfig.forbiddenPaths.join(", ") || "(none)"}`, '- Pi DAG nodes are read-only unless toolProfile="write" is explicitly selected for a bounded writer node.', "- Agent DAG read-only nodes must not write root artifacts/**; root artifacts/ is not a per-node scratchpad.", "- source/references/* are immutable user/source facts; source/需求.md is the derived execution contract.");
238
322
  if (sources.taskConfig.hardConstraints.length > 0) {
239
323
  parts.push("- hardConstraints:", ...sources.taskConfig.hardConstraints.map((c) => ` - ${c}`));
240
324
  }
241
325
  return parts.join("\n\n");
242
326
  }
327
+ async function loadMaterializedSourceReferences(sourceDir) {
328
+ const referenceDir = path.join(sourceDir, REFERENCE_DIRECTORY);
329
+ const referencePaths = [];
330
+ async function collect(dir) {
331
+ let entries;
332
+ try {
333
+ entries = await readdir(dir, { withFileTypes: true });
334
+ }
335
+ catch (error) {
336
+ if (error.code === "ENOENT")
337
+ return;
338
+ throw error;
339
+ }
340
+ for (const entry of entries) {
341
+ const entryPath = path.join(dir, entry.name);
342
+ if (entry.isDirectory()) {
343
+ await collect(entryPath);
344
+ }
345
+ else if (entry.isFile()) {
346
+ // Skip index/manifest sidecars; keep only user/source reference content.
347
+ if (entry.name === "index.json" || entry.name === "source-manifest.json") {
348
+ continue;
349
+ }
350
+ referencePaths.push(entryPath);
351
+ }
352
+ }
353
+ }
354
+ await collect(referenceDir);
355
+ referencePaths.sort((left, right) => left.localeCompare(right));
356
+ return Promise.all(referencePaths.slice(0, MAX_SOURCE_REFERENCE_DOCUMENTS).map(async (filePath) => ({
357
+ path: filePath,
358
+ markdown: await readFile(filePath, "utf-8"),
359
+ })));
360
+ }
243
361
  export async function loadTaskHybridSources(repoRoot, taskId) {
244
362
  const paths = getTaskPaths(repoRoot, taskId);
245
363
  const requirementPath = path.join(paths.sourceDir, REQUIREMENT_FILE);
@@ -265,6 +383,12 @@ export async function loadTaskHybridSources(repoRoot, taskId) {
265
383
  // optional
266
384
  }
267
385
  const taskConfig = await loadTaskConfig(repoRoot, taskId);
386
+ await materializeTaskReferenceDocs({
387
+ repoRoot,
388
+ taskId,
389
+ taskConfig,
390
+ });
391
+ const referenceDocuments = await loadMaterializedSourceReferences(paths.sourceDir);
268
392
  const manifest = await loadHarnessManifest(repoRoot);
269
393
  const strategy = resolveDagVerifyStrategy(taskConfig);
270
394
  let verifyCommands;
@@ -300,6 +424,7 @@ export async function loadTaskHybridSources(repoRoot, taskId) {
300
424
  constraintPath,
301
425
  requirementMarkdown,
302
426
  constraintMarkdown,
427
+ referenceDocuments,
303
428
  taskConfig,
304
429
  enabledExecutors: resolveEnabledExecutors(manifest.executors),
305
430
  executorModelMatrix: resolveExecutorModelMatrices(manifest),
@@ -509,6 +634,14 @@ function buildReviewNode(sources) {
509
634
  "Review upstream implementation and verification evidence.",
510
635
  "First non-empty line must be exactly VERDICT: pass or VERDICT: request-revision.",
511
636
  "List Critical/Important findings when present; any Critical/Important finding must force request-revision. Read-only: do not modify files.",
637
+ [
638
+ "Three-way source fidelity check (required):",
639
+ "1) immutable originals under source/references/ (especially requirement* and acceptance*);",
640
+ "2) derived source/需求.md execution contract (and source/执行约束.md);",
641
+ "3) actual implementation/diff + verification evidence.",
642
+ "If 需求.md conflicts with source/references/*, prefer references and request-revision when the implementation only satisfies the derived summary.",
643
+ "If prompt excerpts are truncated, re-read the Full source paths before verdict.",
644
+ ].join(" "),
512
645
  buildSourceContextBlock(sources),
513
646
  ].join("\n\n"),
514
647
  };
@@ -0,0 +1,52 @@
1
+ function reportError(context, observerIndex, error) {
2
+ const message = error instanceof Error ? error.message : String(error);
3
+ process.stderr.write(`[dag-observer-compose] observer#${observerIndex} ${context} failed: ${message}\n`);
4
+ }
5
+ const HOOK_NAMES = [
6
+ "onRunStart",
7
+ "onNodeStart",
8
+ "onNodeOutput",
9
+ "onNodeFinish",
10
+ "onRunFinish",
11
+ ];
12
+ /**
13
+ * Compose multiple `DagRunObserver`s into one. Each hook is invoked in order.
14
+ * A throw in a single observer's hook is reported to stderr and does not stop
15
+ * subsequent observers — observers are derived views and must not affect
16
+ * canonical DAG execution. Returns `undefined` when no observers are provided.
17
+ */
18
+ export function composeDagRunObservers(observers) {
19
+ const active = observers.filter((observer) => Boolean(observer));
20
+ if (active.length === 0)
21
+ return undefined;
22
+ const observer = {
23
+ onRunStart: async (state) => {
24
+ await runHook("onRunStart", active, (obs) => obs.onRunStart?.(state));
25
+ },
26
+ onNodeStart: async (nodeId, state) => {
27
+ await runHook("onNodeStart", active, (obs) => obs.onNodeStart?.(nodeId, state));
28
+ },
29
+ onNodeOutput: async (nodeId, chunk, state) => {
30
+ await runHook("onNodeOutput", active, (obs) => obs.onNodeOutput?.(nodeId, chunk, state));
31
+ },
32
+ onNodeFinish: async (nodeId, state) => {
33
+ await runHook("onNodeFinish", active, (obs) => obs.onNodeFinish?.(nodeId, state));
34
+ },
35
+ onRunFinish: async (state) => {
36
+ await runHook("onRunFinish", active, (obs) => obs.onRunFinish?.(state));
37
+ },
38
+ };
39
+ return observer;
40
+ }
41
+ async function runHook(hookName, active, invoke) {
42
+ for (let i = 0; i < active.length; i += 1) {
43
+ try {
44
+ await invoke(active[i]);
45
+ }
46
+ catch (error) {
47
+ reportError(hookName, i, error);
48
+ }
49
+ }
50
+ }
51
+ // Re-exported for tests/inspection of hook coverage.
52
+ export const COMPOSED_HOOK_NAMES = HOOK_NAMES;
package/docs/README.md CHANGED
@@ -1,72 +1,74 @@
1
- # 文档索引
2
-
3
- `docs/` 是 loop-agent 的治理根目录,包含工作流规则、方法论、验证规则、执行计划、报告、进度日志、决策记录和可复用模板。
4
-
5
- 顶层 `AGENTS.md` 是操作地图。长期知识应落在此处:决策、契约、计划、验证证据、调试笔记和可复用流程规则应记录在 `docs/` 下,而不是只留在聊天里。
6
-
7
- ## 核心文档
8
-
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 路线图与适配分析
18
- - `init-surface.manifest.json` — npm 包范围、目标项目初始化投影与 `init check-update` surface 分类的机器校验契约
19
-
20
- ## 设计思想来源
21
-
22
- - `../website/docs/practices/` — Anthropic 长时运行 agent harness、OpenAI Codex harness engineering、腾讯端到端 Harness Engineering 与社区 agent harness 实践资料。当前仓库的“人类掌舵、智能体执行”、仓库即记录系统、小步增量、结构化 handoff 和 shell verification 纪律均受这些实践启发;权威执行规则仍以本目录治理文档、根目录 AGENTS.md、harness.json、skills 目录和脚本检查为准。
23
-
24
- ## 方法论
25
-
26
- - `harness-methodology-tdd.md` — 行为变更与 bug 修复的 TDD 纪律
27
- - `harness-methodology-verification.md` — 完成声明前的验证纪律
28
- - `harness-methodology-debugging.md` — 修复前的系统化调试工作流
29
-
30
- ## 产物目录
31
-
32
- - `design/README.md` — 设计草稿与实现契约
33
- - `exec-plans/active/README.md` — 进行中的执行计划
34
- - `exec-plans/completed/README.md` — 已完成的执行计划
35
- - `progress/README.md` — 进度交接日志
36
- - `reports/README.md` — 验证与审计报告
37
- - `decisions/README.md` — 架构决策
38
- - `skills/README.md` — repo-local skill registry and vetting notes
39
- - `templates/` — 可复用的规划、报告与 DAG 模板
40
-
41
- ## 仓库 Skills
42
-
43
- - `../skills/loop-agent/` — loop-agent 自身的 skill 指令与参考资料
44
- - 每个额外 skill 在仓库根 `../skills/` 下使用独立子目录;这些本地副本由 DAG 模板引用,维护不依赖外部 agent skill 目录
45
-
46
- ## 模板
47
-
48
- - `templates/project-start-checklist.md` — 开工前检查清单
49
- - `templates/feature-spec.md` — 有边界的功能规格
50
- - `templates/sprint-contract.md` — 实现契约与验收标准
51
- - `templates/exec-plan.md` — 非平凡工作的执行计划
52
- - `templates/progress-log.md` — 进度与交接日志
53
- - `templates/qa-report.md` — 验证与 QA 证据
54
- - `templates/production-readiness-checklist.md` — 低/中风险单仓库 DAG readiness 检查清单
55
- - `templates/init-evolution-review.md` — 初始化能力演化审查报告模板
56
- - `templates/adr.md` — 架构决策记录(ADR)
57
-
58
- ## 维护
59
-
60
- 文档变更后运行:
61
-
62
- ```bash
63
- bash scripts/check-repo.sh
64
- ```
65
-
66
- Windows 上通过 Git Bash 或已配置的兼容 Bash 运行脚本。实际文件操作使用平台原生路径;`/` 仅用于 repo 引用、JSON/Markdown 证据引用和 glob 约定。
67
-
68
- 完整本地门禁:
69
-
70
- ```bash
71
- bash scripts/ci.sh
72
- ```
1
+ # 文档索引
2
+
3
+ `docs/` 是 loop-agent 的治理根目录,包含工作流规则、方法论、验证规则、执行计划、报告、进度日志、决策记录和可复用模板。
4
+
5
+ 顶层 `AGENTS.md` 是操作地图。长期知识应落在此处:决策、契约、计划、验证证据、调试笔记和可复用流程规则应记录在 `docs/` 下,而不是只留在聊天里。
6
+
7
+ ## 核心文档
8
+
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 路线图与适配分析
18
+ - `init-surface.manifest.json` — npm 包范围、目标项目初始化投影与 `init check-update` surface 分类的机器校验契约
19
+
20
+ ## 设计思想来源
21
+
22
+ - `../website/docs/practices/` — Anthropic 长时运行 agent harness、OpenAI Codex harness engineering、腾讯端到端 Harness Engineering 与社区 agent harness 实践资料。当前仓库的“人类掌舵、智能体执行”、仓库即记录系统、小步增量、结构化 handoff 和 shell verification 纪律均受这些实践启发;权威执行规则仍以本目录治理文档、根目录 AGENTS.md、harness.json、skills 目录和脚本检查为准。
23
+
24
+ ## 方法论
25
+
26
+ - `harness-methodology-tdd.md` — 行为变更与 bug 修复的 TDD 纪律
27
+ - `harness-methodology-verification.md` — 完成声明前的验证纪律
28
+ - `harness-methodology-debugging.md` — 修复前的系统化调试工作流
29
+
30
+ ## 产物目录
31
+
32
+ - `design/README.md` — 设计草稿与实现契约
33
+ - `exec-plans/active/README.md` — 进行中的执行计划
34
+ - `exec-plans/completed/README.md` — 已完成的执行计划
35
+ - `progress/README.md` — 进度交接日志
36
+ - `reports/README.md` — 验证与审计报告
37
+ - `decisions/README.md` — 架构决策
38
+ - `skills/README.md` — repo-local skill registry and vetting notes
39
+ - `templates/` — 可复用的规划、报告与 DAG 模板
40
+
41
+ ## 仓库 Skills
42
+
43
+ - `../skills/loop-agent/` — loop-agent 自身的 skill 指令与参考资料
44
+ - 每个额外 skill 在仓库根 `../skills/` 下使用独立子目录;这些本地副本由 DAG 模板引用,维护不依赖外部 agent skill 目录
45
+
46
+ ## 模板
47
+
48
+ - `templates/project-start-checklist.md` — 开工前检查清单
49
+ - `templates/feature-spec.md` — 有边界的功能规格
50
+ - `templates/sprint-contract.md` — 实现契约与验收标准
51
+ - `templates/exec-plan.md` — 非平凡工作的执行计划
52
+ - `templates/progress-log.md` — 进度与交接日志
53
+ - `templates/qa-report.md` — 验证与 QA 证据
54
+ - `templates/worker-dogfood-setup.md` — 发布控制器下的真实 Worker sample setup 与 retry 纪律
55
+ - `templates/worker-dogfood-evidence.md` — BE/FE/QA sample、Observe、morning report 与 coverage evidence 模板
56
+ - `templates/production-readiness-checklist.md` — 低/中风险单仓库 DAG readiness 检查清单
57
+ - `templates/init-evolution-review.md` — 初始化能力演化审查报告模板
58
+ - `templates/adr.md` — 架构决策记录(ADR)
59
+
60
+ ## 维护
61
+
62
+ 文档变更后运行:
63
+
64
+ ```bash
65
+ bash scripts/check-repo.sh
66
+ ```
67
+
68
+ Windows 上通过 Git Bash 或已配置的兼容 Bash 运行脚本。实际文件操作使用平台原生路径;`/` 仅用于 repo 引用、JSON/Markdown 证据引用和 glob 约定。
69
+
70
+ 完整本地门禁:
71
+
72
+ ```bash
73
+ bash scripts/ci.sh
74
+ ```